Statistics
| Branch: | Tag: | Revision:

root / tools / lvmstrap @ 5e861051

History | View | Annotate | Download (25.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
import errno
48

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

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

    
59
verbose_flag = False
60

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

    
69
#: Excluded filesystem types
70
EXCLUDED_FS = frozenset([
71
  "nfs",
72
  "nfs4",
73
  "autofs",
74
  "tmpfs",
75
  "proc",
76
  "sysfs",
77
  "usbfs",
78
  "devpts",
79
  ])
80

    
81

    
82
class Error(Exception):
83
  """Generic exception"""
84
  pass
85

    
86

    
87
class ProgrammingError(Error):
88
  """Exception denoting invalid assumptions in programming.
89

    
90
  This should catch sysfs tree changes, or otherwise incorrect
91
  assumptions about the contents of the /sys/block/... directories.
92

    
93
  """
94
  pass
95

    
96

    
97
class SysconfigError(Error):
98
  """Exception denoting invalid system configuration.
99

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

    
104
  This should usually mean that the installation of the Xen node
105
  failed in some steps.
106

    
107
  """
108
  pass
109

    
110

    
111
class PrereqError(Error):
112
  """Exception denoting invalid prerequisites.
113

    
114
  If the node does not meet the requirements for cluster membership, this
115
  exception will be raised. Things like wrong kernel version, or no
116
  free disks, etc. belong here.
117

    
118
  This should usually mean that the build steps for the Xen node were
119
  not followed correctly.
120

    
121
  """
122
  pass
123

    
124

    
125
class OperationalError(Error):
126
  """Exception denoting actual errors.
127

    
128
  Errors during the bootstrapping are signaled using this exception.
129

    
130
  """
131
  pass
132

    
133

    
134
class ParameterError(Error):
135
  """Exception denoting invalid input from user.
136

    
137
  Wrong disks given as parameters will be signaled using this
138
  exception.
139

    
140
  """
141
  pass
142

    
143

    
144
def Usage():
145
  """Shows program usage information and exits the program.
146

    
147
  """
148
  print >> sys.stderr, "Usage:"
149
  print >> sys.stderr, USAGE
150
  sys.exit(2)
151

    
152

    
153
def ParseOptions():
154
  """Parses the command line options.
155

    
156
  In case of command line errors, it will show the usage and exit the
157
  program.
158

    
159
  @rtype: tuple
160
  @return: a tuple of (options, args), as returned by
161
      OptionParser.parse_args
162

    
163
  """
164
  global verbose_flag # pylint: disable-msg=W0603
165

    
166
  parser = optparse.OptionParser(usage="\n%s" % USAGE,
167
                                 version="%%prog (ganeti) %s" %
168
                                 constants.RELEASE_VERSION)
169

    
170
  parser.add_option("--alldisks", dest="alldisks",
171
                    help="erase ALL disks", action="store_true",
172
                    default=False)
173
  parser.add_option("-d", "--disks", dest="disks",
174
                    help="Choose disks (e.g. hda,hdg)",
175
                    metavar="DISKLIST")
176
  parser.add_option(cli.VERBOSE_OPT)
177
  parser.add_option("-r", "--allow-removable",
178
                    action="store_true", dest="removable_ok", default=False,
179
                    help="allow and use removable devices too")
180
  parser.add_option("-g", "--vg-name", type="string",
181
                    dest="vgname", default="xenvg", metavar="NAME",
182
                    help="the volume group to be created [default: xenvg]")
183

    
184

    
185
  options, args = parser.parse_args()
186
  if len(args) != 1:
187
    Usage()
188

    
189
  verbose_flag = options.verbose
190

    
191
  return options, args
192

    
193

    
194
def IsPartitioned(disk):
195
  """Returns whether a given disk should be used partitioned or as-is.
196

    
197
  Currently only md devices are used as is.
198

    
199
  """
200
  return not disk.startswith('md')
201

    
202

    
203
def DeviceName(disk):
204
  """Returns the appropriate device name for a disk.
205

    
206
  For non-partitioned devices, it returns the name as is, otherwise it
207
  returns the first partition.
208

    
209
  """
210
  if IsPartitioned(disk):
211
    device = '/dev/%s1' % disk
212
  else:
213
    device = '/dev/%s' % disk
214
  return device
215

    
216

    
217
def ExecCommand(command):
218
  """Executes a command.
219

    
220
  This is just a wrapper around commands.getstatusoutput, with the
221
  difference that if the command line argument -v has been given, it
222
  will print the command line and the command output on stdout.
223

    
224
  @param command: the command line to be executed
225
  @rtype: tuple
226
  @return: a tuple of (status, output) where status is the exit status
227
      and output the stdout and stderr of the command together
228

    
229
  """
230
  if verbose_flag:
231
    print command
232
  result = RunCmd(command)
233
  if verbose_flag:
234
    print result.output
235
  return result
236

    
237

    
238
def CheckPrereq():
239
  """Check the prerequisites of this program.
240

    
241
  It check that it runs on Linux 2.6, and that /sys is mounted and the
242
  fact that /sys/block is a directory.
243

    
244
  """
245
  if os.getuid() != 0:
246
    raise PrereqError("This tool runs as root only. Really.")
247

    
248
  osname, _, release, _, _ = os.uname()
249
  if osname != 'Linux':
250
    raise PrereqError("This tool only runs on Linux"
251
                      " (detected OS: %s)." % osname)
252

    
253
  if not release.startswith("2.6."):
254
    raise PrereqError("Wrong major kernel version (detected %s, needs"
255
                      " 2.6.*)" % release)
256

    
257
  if not os.path.ismount("/sys"):
258
    raise PrereqError("Can't find a filesystem mounted at /sys."
259
                      " Please mount /sys.")
260

    
261
  if not os.path.isdir("/sys/block"):
262
    raise SysconfigError("Can't find /sys/block directory. Has the"
263
                         " layout of /sys changed?")
264

    
265
  if not os.path.ismount("/proc"):
266
    raise PrereqError("Can't find a filesystem mounted at /proc."
267
                      " Please mount /proc.")
268

    
269
  if not os.path.exists("/proc/mounts"):
270
    raise SysconfigError("Can't find /proc/mounts")
271

    
272

    
273
def CheckVGExists(vgname):
274
  """Checks to see if a volume group exists.
275

    
276
  @param vgname: the volume group name
277

    
278
  @return: a four-tuple (exists, lv_count, vg_size, vg_free), where:
279
      - exists: True if the volume exists, otherwise False; if False,
280
        all other members of the tuple are None
281
      - lv_count: The number of logical volumes in the volume group
282
      - vg_size: The total size of the volume group (in gibibytes)
283
      - vg_free: The available space in the volume group
284

    
285
  """
286
  result = ExecCommand("vgs --nohead -o lv_count,vg_size,vg_free"
287
                       " --nosuffix --units g"
288
                       " --ignorelockingfailure %s" % vgname)
289
  if not result.failed:
290
    try:
291
      lv_count, vg_size, vg_free = result.stdout.strip().split()
292
    except ValueError:
293
      # This means the output of vgdisplay can't be parsed
294
      raise PrereqError("cannot parse output of vgs (%s)" % result.stdout)
295
  else:
296
    lv_count = vg_size = vg_free = None
297

    
298
  return not result.failed, lv_count, vg_size, vg_free
299

    
300

    
301
def CheckSysDev(name, devnum):
302
  """Checks consistency between /sys and /dev trees.
303

    
304
  In /sys/block/<name>/dev and /sys/block/<name>/<part>/dev are the
305
  kernel-known device numbers. The /dev/<name> block/char devices are
306
  created by userspace and thus could differ from the kernel
307
  view. This function checks the consistency between the device number
308
  read from /sys and the actual device number in /dev.
309

    
310
  Note that since the system could be using udev which removes and
311
  recreates the device nodes on partition table rescan, we need to do
312
  some retries here. Since we only do a stat, we can afford to do many
313
  short retries.
314

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

    
319
  """
320
  path = "/dev/%s" % name
321
  for _ in range(40):
322
    if os.path.exists(path):
323
      break
324
    time.sleep(0.250)
325
  else:
326
    raise SysconfigError("the device file %s does not exist, but the block"
327
                         " device exists in the /sys/block tree" % path)
328
  rdev = os.stat(path).st_rdev
329
  if devnum != rdev:
330
    raise SysconfigError("For device %s, the major:minor in /dev is %04x"
331
                         " while the major:minor in sysfs is %s" %
332
                         (path, rdev, devnum))
333

    
334

    
335
def ReadDev(syspath):
336
  """Reads the device number from a sysfs path.
337

    
338
  The device number is given in sysfs under a block device directory
339
  in a file named 'dev' which contains major:minor (in ASCII). This
340
  function reads that file and converts the major:minor pair to a dev
341
  number.
342

    
343
  @type syspath: string
344
  @param syspath: the path to a block device dir in sysfs,
345
      e.g. C{/sys/block/sda}
346

    
347
  @return: the device number
348

    
349
  """
350
  if not os.path.exists("%s/dev" % syspath):
351
    raise ProgrammingError("Invalid path passed to ReadDev: %s" % syspath)
352
  f = open("%s/dev" % syspath)
353
  data = f.read().strip()
354
  f.close()
355
  major, minor = data.split(":", 1)
356
  major = int(major)
357
  minor = int(minor)
358
  dev = os.makedev(major, minor)
359
  return dev
360

    
361

    
362
def ReadSize(syspath):
363
  """Reads the size from a sysfs path.
364

    
365
  The size is given in sysfs under a block device directory in a file
366
  named 'size' which contains the number of sectors (in ASCII). This
367
  function reads that file and converts the number in sectors to the
368
  size in bytes.
369

    
370
  @type syspath: string
371
  @param syspath: the path to a block device dir in sysfs,
372
      e.g. C{/sys/block/sda}
373

    
374
  @rtype: int
375
  @return: the device size in bytes
376

    
377
  """
378

    
379
  if not os.path.exists("%s/size" % syspath):
380
    raise ProgrammingError("Invalid path passed to ReadSize: %s" % syspath)
381
  f = open("%s/size" % syspath)
382
  data = f.read().strip()
383
  f.close()
384
  size = 512L * int(data)
385
  return size
386

    
387

    
388
def ReadPV(name):
389
  """Reads physical volume information.
390

    
391
  This function tries to see if a block device is a physical volume.
392

    
393
  @type name: string
394
  @param name: the device name (e.g. sda)
395

    
396
  @return: the name of the volume group to which this PV belongs, or
397
      "" if this PV is not in use, or None if this is not a PV
398

    
399
  """
400
  result = ExecCommand("pvdisplay -c /dev/%s" % name)
401
  if result.failed:
402
    return None
403
  vgname = result.stdout.strip().split(":")[1]
404
  return vgname
405

    
406

    
407
def GetDiskList(opts):
408
  """Computes the block device list for this system.
409

    
410
  This function examines the /sys/block tree and using information
411
  therein, computes the status of the block device.
412

    
413
  @return: a list like [(name, size, dev, partitions, inuse), ...], where:
414
      - name is the block device name (e.g. sda)
415
      - size the size in bytes
416
      - dev is the device number (e.g. 8704 for hdg)
417
      - partitions is [(name, size, dev), ...] mirroring the disk list
418
        data inuse is a boolean showing the in-use status of the disk,
419
        computed as the possibility of re-reading the partition table
420
        (the meaning of the operation varies with the kernel version,
421
        but is usually accurate; a mounted disk/partition or swap-area
422
        or PV with active LVs on it is busy)
423

    
424
  """
425
  dlist = []
426
  for name in os.listdir("/sys/block"):
427
    if not compat.any([name.startswith(pfx) for pfx in SUPPORTED_TYPES]):
428
      continue
429

    
430
    size = ReadSize("/sys/block/%s" % name)
431

    
432
    f = open("/sys/block/%s/removable" % name)
433
    removable = int(f.read().strip())
434
    f.close()
435

    
436
    if removable and not opts.removable_ok:
437
      continue
438

    
439
    dev = ReadDev("/sys/block/%s" % name)
440
    CheckSysDev(name, dev)
441
    inuse = InUse(name)
442
    # Enumerate partitions of the block device
443
    partitions = []
444
    for partname in os.listdir("/sys/block/%s" % name):
445
      if not partname.startswith(name):
446
        continue
447
      partdev = ReadDev("/sys/block/%s/%s" % (name, partname))
448
      partsize = ReadSize("/sys/block/%s/%s" % (name, partname))
449
      CheckSysDev(partname, partdev)
450
      partitions.append((partname, partsize, partdev))
451
    partitions.sort()
452
    dlist.append((name, size, dev, partitions, inuse))
453
  dlist.sort()
454
  return dlist
455

    
456

    
457
def GetMountInfo():
458
  """Reads /proc/mounts and computes the mountpoint-devnum mapping.
459

    
460
  This function reads /proc/mounts, finds the mounted filesystems
461
  (excepting a hard-coded blacklist of network and virtual
462
  filesystems) and does a stat on these mountpoints. The st_dev number
463
  of the results is memorised for later matching against the
464
  /sys/block devices.
465

    
466
  @rtype: dict
467
  @return: a {mountpoint: device number} dictionary
468

    
469
  """
470
  mountlines = ReadFile("/proc/mounts").splitlines()
471
  mounts = {}
472
  for line in mountlines:
473
    _, mountpoint, fstype, _ = line.split(None, 3)
474
    # fs type blacklist
475
    if fstype in EXCLUDED_FS:
476
      continue
477
    try:
478
      dev = os.stat(mountpoint).st_dev
479
    except OSError, err:
480
      # this should be a fairly rare error, since we are blacklisting
481
      # network filesystems; with this in mind, we'll ignore it,
482
      # since the rereadpt check catches in-use filesystems,
483
      # and this is used for disk information only
484
      print >> sys.stderr, ("Can't stat mountpoint '%s': %s" %
485
                            (mountpoint, err))
486
      print >> sys.stderr, "Ignoring."
487
      continue
488
    mounts[dev] = mountpoint
489
  return mounts
490

    
491

    
492
def DevInfo(name, dev, mountinfo):
493
  """Computes miscellaneous information about a block device.
494

    
495
  @type name: string
496
  @param name: the device name, e.g. sda
497

    
498
  @return: a tuple (mpath, whatvg, fileinfo), where:
499
      - mpath is the mount path where this device is mounted or None
500
      - whatvg is the result of the ReadPV function
501
      - fileinfo is the output of file -bs on the device
502

    
503
  """
504
  if dev in mountinfo:
505
    mpath = mountinfo[dev]
506
  else:
507
    mpath = None
508

    
509
  whatvg = ReadPV(name)
510

    
511
  result = ExecCommand("file -bs /dev/%s" % name)
512
  if result.failed:
513
    fileinfo = "<error: %s>" % result.stderr
514
  fileinfo = result.stdout[:45]
515
  return mpath, whatvg, fileinfo
516

    
517

    
518
def ShowDiskInfo(opts):
519
  """Shows a nicely formatted block device list for this system.
520

    
521
  This function shows the user a table with the information gathered
522
  by the other functions defined, in order to help the user make a
523
  choice about which disks should be allocated to our volume group.
524

    
525
  """
526
  mounts = GetMountInfo()
527
  dlist = GetDiskList(opts)
528

    
529
  print "------- Disk information -------"
530
  headers = {
531
      "name": "Name",
532
      "size": "Size[M]",
533
      "used": "Used",
534
      "mount": "Mount",
535
      "lvm": "LVM?",
536
      "info": "Info"
537
      }
538
  fields = ["name", "size", "used", "mount", "lvm", "info"]
539

    
540
  flatlist = []
541
  # Flatten the [(disk, [partition,...]), ...] list
542
  for name, size, dev, parts, inuse in dlist:
543
    if inuse:
544
      str_inuse = "yes"
545
    else:
546
      str_inuse = "no"
547
    flatlist.append((name, size, dev, str_inuse))
548
    for partname, partsize, partdev in parts:
549
      flatlist.append((partname, partsize, partdev, ""))
550

    
551
  strlist = []
552
  for name, size, dev, in_use in flatlist:
553
    mp, vgname, fileinfo = DevInfo(name, dev, mounts)
554
    if mp is None:
555
      mp = "-"
556
    if vgname is None:
557
      lvminfo = "-"
558
    elif vgname == "":
559
      lvminfo = "yes,free"
560
    else:
561
      lvminfo = "in %s" % vgname
562

    
563
    if len(name) > 3:
564
      # Indent partitions
565
      name = " %s" % name
566

    
567
    strlist.append([name, "%.2f" % (float(size) / 1024 / 1024),
568
                    in_use, mp, lvminfo, fileinfo])
569

    
570
  data = cli.GenerateTable(headers, fields, None,
571
                           strlist, numfields=["size"])
572

    
573
  for line in data:
574
    print line
575

    
576

    
577
def CheckSysfsHolders(name):
578
  """Check to see if a device is 'hold' at sysfs level.
579

    
580
  This is usually the case for Physical Volumes under LVM.
581

    
582
  @rtype: boolean
583
  @return: true if the device is available according to sysfs
584

    
585
  """
586
  try:
587
    contents = os.listdir("/sys/block/%s/holders/" % name)
588
  except OSError, err:
589
    if err.errno == errno.ENOENT:
590
      contents = []
591
    else:
592
      raise
593
  return not bool(contents)
594

    
595

    
596
def CheckReread(name):
597
  """Check to see if a block device is in use.
598

    
599
  Uses blockdev to reread the partition table of a block device (or
600
  fuser if the device is not partitionable), and thus compute the
601
  in-use status.  See the discussion in GetDiskList about the meaning
602
  of 'in use'.
603

    
604
  @rtype: boolean
605
  @return: the in-use status of the device
606

    
607
  """
608
  use_blockdev = IsPartitioned(name)
609
  if use_blockdev:
610
    cmd = "blockdev --rereadpt /dev/%s" % name
611
  else:
612
    cmd = "fuser -vam /dev/%s" % name
613

    
614
  for _ in range(3):
615
    result = ExecCommand(cmd)
616
    if not use_blockdev and result.failed:
617
      break
618
    elif not result.failed:
619
      break
620
    time.sleep(2)
621

    
622
  if use_blockdev:
623
    return not result.failed
624
  else:
625
    return result.failed
626

    
627

    
628
def CheckMounted(name):
629
  """Check to see if a block device is a mountpoint.
630

    
631
  In recent distros/kernels, this is reported directly via fuser, but
632
  on older ones not, so we do an additional check here (manually).
633

    
634
  """
635
  minfo = GetMountInfo()
636
  dev = ReadDev("/sys/block/%s" % name)
637
  return dev not in minfo
638

    
639

    
640
def InUse(name):
641
  """Returns if a disk is in use or not.
642

    
643
  """
644
  return not (CheckSysfsHolders(name) and CheckReread(name) and
645
              CheckMounted(name))
646

    
647

    
648
def WipeDisk(name):
649
  """Wipes a block device.
650

    
651
  This function wipes a block device, by clearing and re-reading the
652
  partition table. If not successful, it writes back the old partition
653
  data, and leaves the cleanup to the user.
654

    
655
  @param name: the device name (e.g. sda)
656

    
657
  """
658

    
659
  if InUse(name):
660
    raise OperationalError("CRITICAL: disk %s you selected seems to be in"
661
                           " use. ABORTING!" % name)
662

    
663
  fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
664
  olddata = os.read(fd, 512)
665
  if len(olddata) != 512:
666
    raise OperationalError("CRITICAL: Can't read partition table information"
667
                           " from /dev/%s (needed 512 bytes, got %d" %
668
                           (name, len(olddata)))
669
  newdata = "\0" * 512
670
  os.lseek(fd, 0, 0)
671
  bytes_written = os.write(fd, newdata)
672
  os.close(fd)
673
  if bytes_written != 512:
674
    raise OperationalError("CRITICAL: Can't write partition table information"
675
                           " to /dev/%s (tried to write 512 bytes, written"
676
                           " %d. I don't know how to cleanup. Sorry." %
677
                           (name, bytes_written))
678

    
679
  if InUse(name):
680
    # try to restore the data
681
    fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
682
    os.write(fd, olddata)
683
    os.close(fd)
684
    raise OperationalError("CRITICAL: disk %s which I have just wiped cannot"
685
                           " reread partition table. Most likely, it is"
686
                           " in use. You have to clean after this yourself."
687
                           " I tried to restore the old partition table,"
688
                           " but I cannot guarantee nothing has broken." %
689
                           name)
690

    
691

    
692
def PartitionDisk(name):
693
  """Partitions a disk.
694

    
695
  This function creates a single partition spanning the entire disk,
696
  by means of fdisk.
697

    
698
  @param name: the device name, e.g. sda
699

    
700
  """
701
  result = ExecCommand(
702
    'echo ,,8e, | sfdisk /dev/%s' % name)
703
  if result.failed:
704
    raise OperationalError("CRITICAL: disk %s which I have just partitioned"
705
                           " cannot reread its partition table, or there"
706
                           " is some other sfdisk error. Likely, it is in"
707
                           " use. You have to clean this yourself. Error"
708
                           " message from sfdisk: %s" %
709
                           (name, result.output))
710

    
711

    
712
def CreatePVOnDisk(name):
713
  """Creates a physical volume on a block device.
714

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

    
718
  @param name: the device name, e.g. sda
719

    
720
  """
721
  device = DeviceName(name)
722
  result = ExecCommand("pvcreate -yff %s" % device)
723
  if result.failed:
724
    raise OperationalError("I cannot create a physical volume on"
725
                           " %s. Error message: %s."
726
                           " Please clean up yourself." %
727
                           (device, result.output))
728

    
729

    
730
def CreateVG(vgname, disks):
731
  """Creates the volume group.
732

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

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

    
738
  """
739
  pnames = [DeviceName(d) for d in disks]
740
  result = ExecCommand("vgcreate -s 64MB '%s' %s" % (vgname, " ".join(pnames)))
741
  if result.failed:
742
    raise OperationalError("I cannot create the volume group %s from"
743
                           " disks %s. Error message: %s. Please clean up"
744
                           " yourself." %
745
                           (vgname, " ".join(disks), result.output))
746

    
747

    
748
def ValidateDiskList(options):
749
  """Validates or computes the disk list for create.
750

    
751
  This function either computes the available disk list (if the user
752
  gave --alldisks option), or validates the user-given disk list (by
753
  using the --disks option) such that all given disks are present and
754
  not in use.
755

    
756
  @param options: the options returned from OptParser.parse_options
757

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

    
760
  """
761
  sysdisks = GetDiskList(options)
762
  if not sysdisks:
763
    raise PrereqError("no disks found (I looked for"
764
                      " non-removable block devices).")
765
  sysd_free = []
766
  sysd_used = []
767
  for name, _, _, _, used in sysdisks:
768
    if used:
769
      sysd_used.append(name)
770
    else:
771
      sysd_free.append(name)
772

    
773
  if not sysd_free:
774
    raise PrereqError("no free disks found! (%d in-use disks)" %
775
                      len(sysd_used))
776
  if options.alldisks:
777
    disklist = sysd_free
778
  elif options.disks:
779
    disklist = options.disks.split(",")
780
    for name in disklist:
781
      if name in sysd_used:
782
        raise ParameterError("disk %s is in use, cannot wipe!" % name)
783
      if name not in sysd_free:
784
        raise ParameterError("cannot find disk %s!" % name)
785
  else:
786
    raise ParameterError("Please use either --alldisks or --disks!")
787

    
788
  return disklist
789

    
790

    
791
def BootStrap():
792
  """Actual main routine.
793

    
794
  """
795
  CheckPrereq()
796

    
797
  options, args = ParseOptions()
798
  vgname = options.vgname
799
  command = args.pop(0)
800
  if command == "diskinfo":
801
    ShowDiskInfo(options)
802
    return
803
  if command != "create":
804
    Usage()
805

    
806
  exists, lv_count, vg_size, vg_free = CheckVGExists(vgname)
807
  if exists:
808
    raise PrereqError("It seems volume group '%s' already exists:\n"
809
                      "  LV count: %s, size: %s, free: %s." %
810
                      (vgname, lv_count, vg_size, vg_free))
811

    
812

    
813
  disklist = ValidateDiskList(options)
814

    
815
  for disk in disklist:
816
    WipeDisk(disk)
817
    if IsPartitioned(disk):
818
      PartitionDisk(disk)
819
  for disk in disklist:
820
    CreatePVOnDisk(disk)
821
  CreateVG(vgname, disklist)
822

    
823
  status, lv_count, size, _ = CheckVGExists(vgname)
824
  if status:
825
    print "Done! %s: size %s GiB, disks: %s" % (vgname, size,
826
                                              ",".join(disklist))
827
  else:
828
    raise OperationalError("Although everything seemed ok, the volume"
829
                           " group did not get created.")
830

    
831

    
832
def main():
833
  """Application entry point.
834

    
835
  This is just a wrapper over BootStrap, to handle our own exceptions.
836

    
837
  """
838
  try:
839
    BootStrap()
840
  except PrereqError, err:
841
    print >> sys.stderr, "The prerequisites for running this tool are not met."
842
    print >> sys.stderr, ("Please make sure you followed all the steps in"
843
                          " the build document.")
844
    print >> sys.stderr, "Description: %s" % str(err)
845
    sys.exit(1)
846
  except SysconfigError, err:
847
    print >> sys.stderr, ("This system's configuration seems wrong, at"
848
                          " least is not what I expect.")
849
    print >> sys.stderr, ("Please check that the installation didn't fail"
850
                          " at some step.")
851
    print >> sys.stderr, "Description: %s" % str(err)
852
    sys.exit(1)
853
  except ParameterError, err:
854
    print >> sys.stderr, ("Some parameters you gave to the program or the"
855
                          " invocation is wrong. ")
856
    print >> sys.stderr, "Description: %s" % str(err)
857
    Usage()
858
  except OperationalError, err:
859
    print >> sys.stderr, ("A serious error has happened while modifying"
860
                          " the system's configuration.")
861
    print >> sys.stderr, ("Please review the error message below and make"
862
                          " sure you clean up yourself.")
863
    print >> sys.stderr, ("It is most likely that the system configuration"
864
                          " has been partially altered.")
865
    print >> sys.stderr, str(err)
866
    sys.exit(1)
867
  except ProgrammingError, err:
868
    print >> sys.stderr, ("Internal application error. Please report this"
869
                          " to the Ganeti developer list.")
870
    print >> sys.stderr, "Error description: %s" % str(err)
871
    sys.exit(1)
872
  except Error, err:
873
    print >> sys.stderr, "Unhandled application error: %s" % err
874
    sys.exit(1)
875
  except (IOError, OSError), err:
876
    print >> sys.stderr, "I/O error detected, please report."
877
    print >> sys.stderr, "Description: %s" % str(err)
878
    sys.exit(1)
879

    
880

    
881
if __name__ == "__main__":
882
  main()