lvmstrap: add PV-on-partition support
[ganeti-local] / tools / lvmstrap
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 import re
49
50 from ganeti.utils import RunCmd, ReadFile
51 from ganeti import constants
52 from ganeti import cli
53 from ganeti import compat
54
55 USAGE = ("\tlvmstrap diskinfo\n"
56          "\tlvmstrap [--vgname=NAME] [--allow-removable]"
57          " { --alldisks | --disks DISKLIST }"
58          " create")
59
60 verbose_flag = False
61
62 #: Supported disk types (as prefixes)
63 SUPPORTED_TYPES = [
64   "hd",
65   "sd",
66   "md",
67   "ubd",
68   ]
69
70 #: Excluded filesystem types
71 EXCLUDED_FS = frozenset([
72   "nfs",
73   "nfs4",
74   "autofs",
75   "tmpfs",
76   "proc",
77   "sysfs",
78   "usbfs",
79   "devpts",
80   ])
81
82 #: A regular expression that matches partitions (must be kept in sync
83 # with L{SUPPORTED_TYPES}
84 PART_RE = re.compile("^((?:h|s|m|ub)d[a-z]{1,2})[0-9]+$")
85
86 #: Minimum partition size to be considered (1 GB)
87 PART_MINSIZE = 1024 * 1024 * 1024
88
89
90 class Error(Exception):
91   """Generic exception"""
92   pass
93
94
95 class ProgrammingError(Error):
96   """Exception denoting invalid assumptions in programming.
97
98   This should catch sysfs tree changes, or otherwise incorrect
99   assumptions about the contents of the /sys/block/... directories.
100
101   """
102   pass
103
104
105 class SysconfigError(Error):
106   """Exception denoting invalid system configuration.
107
108   If the system configuration is somehow wrong (e.g. /dev files
109   missing, or having mismatched major/minor numbers relative to
110   /sys/block devices), this exception will be raised.
111
112   This should usually mean that the installation of the Xen node
113   failed in some steps.
114
115   """
116   pass
117
118
119 class PrereqError(Error):
120   """Exception denoting invalid prerequisites.
121
122   If the node does not meet the requirements for cluster membership, this
123   exception will be raised. Things like wrong kernel version, or no
124   free disks, etc. belong here.
125
126   This should usually mean that the build steps for the Xen node were
127   not followed correctly.
128
129   """
130   pass
131
132
133 class OperationalError(Error):
134   """Exception denoting actual errors.
135
136   Errors during the bootstrapping are signaled using this exception.
137
138   """
139   pass
140
141
142 class ParameterError(Error):
143   """Exception denoting invalid input from user.
144
145   Wrong disks given as parameters will be signaled using this
146   exception.
147
148   """
149   pass
150
151
152 def Usage():
153   """Shows program usage information and exits the program.
154
155   """
156   print >> sys.stderr, "Usage:"
157   print >> sys.stderr, USAGE
158   sys.exit(2)
159
160
161 def ParseOptions():
162   """Parses the command line options.
163
164   In case of command line errors, it will show the usage and exit the
165   program.
166
167   @rtype: tuple
168   @return: a tuple of (options, args), as returned by
169       OptionParser.parse_args
170
171   """
172   global verbose_flag # pylint: disable-msg=W0603
173
174   parser = optparse.OptionParser(usage="\n%s" % USAGE,
175                                  version="%%prog (ganeti) %s" %
176                                  constants.RELEASE_VERSION)
177
178   parser.add_option("--alldisks", dest="alldisks",
179                     help="erase ALL disks", action="store_true",
180                     default=False)
181   parser.add_option("-d", "--disks", dest="disks",
182                     help="Choose disks (e.g. hda,hdg)",
183                     metavar="DISKLIST")
184   parser.add_option(cli.VERBOSE_OPT)
185   parser.add_option("-r", "--allow-removable",
186                     action="store_true", dest="removable_ok", default=False,
187                     help="allow and use removable devices too")
188   parser.add_option("-g", "--vg-name", type="string",
189                     dest="vgname", default="xenvg", metavar="NAME",
190                     help="the volume group to be created [default: xenvg]")
191
192
193   options, args = parser.parse_args()
194   if len(args) != 1:
195     Usage()
196
197   verbose_flag = options.verbose
198
199   return options, args
200
201
202 def IsPartitioned(disk):
203   """Returns whether a given disk should be used partitioned or as-is.
204
205   Currently only md devices are used as is.
206
207   """
208   return not (disk.startswith('md') or PART_RE.match(disk))
209
210
211 def DeviceName(disk):
212   """Returns the appropriate device name for a disk.
213
214   For non-partitioned devices, it returns the name as is, otherwise it
215   returns the first partition.
216
217   """
218   if IsPartitioned(disk):
219     device = '/dev/%s1' % disk
220   else:
221     device = '/dev/%s' % disk
222   return device
223
224
225 def SysfsName(disk):
226   """Returns the sysfs name for a disk or partition.
227
228   """
229   match = PART_RE.match(disk)
230   if match:
231     # this is a partition, which resides in /sys/block under a different name
232     disk = "%s/%s"  % (match.group(1), disk)
233   return "/sys/block/%s" % disk
234
235
236 def ExecCommand(command):
237   """Executes a command.
238
239   This is just a wrapper around commands.getstatusoutput, with the
240   difference that if the command line argument -v has been given, it
241   will print the command line and the command output on stdout.
242
243   @param command: the command line to be executed
244   @rtype: tuple
245   @return: a tuple of (status, output) where status is the exit status
246       and output the stdout and stderr of the command together
247
248   """
249   if verbose_flag:
250     print command
251   result = RunCmd(command)
252   if verbose_flag:
253     print result.output
254   return result
255
256
257 def CheckPrereq():
258   """Check the prerequisites of this program.
259
260   It check that it runs on Linux 2.6, and that /sys is mounted and the
261   fact that /sys/block is a directory.
262
263   """
264   if os.getuid() != 0:
265     raise PrereqError("This tool runs as root only. Really.")
266
267   osname, _, release, _, _ = os.uname()
268   if osname != 'Linux':
269     raise PrereqError("This tool only runs on Linux"
270                       " (detected OS: %s)." % osname)
271
272   if not release.startswith("2.6."):
273     raise PrereqError("Wrong major kernel version (detected %s, needs"
274                       " 2.6.*)" % release)
275
276   if not os.path.ismount("/sys"):
277     raise PrereqError("Can't find a filesystem mounted at /sys."
278                       " Please mount /sys.")
279
280   if not os.path.isdir("/sys/block"):
281     raise SysconfigError("Can't find /sys/block directory. Has the"
282                          " layout of /sys changed?")
283
284   if not os.path.ismount("/proc"):
285     raise PrereqError("Can't find a filesystem mounted at /proc."
286                       " Please mount /proc.")
287
288   if not os.path.exists("/proc/mounts"):
289     raise SysconfigError("Can't find /proc/mounts")
290
291
292 def CheckVGExists(vgname):
293   """Checks to see if a volume group exists.
294
295   @param vgname: the volume group name
296
297   @return: a four-tuple (exists, lv_count, vg_size, vg_free), where:
298       - exists: True if the volume exists, otherwise False; if False,
299         all other members of the tuple are None
300       - lv_count: The number of logical volumes in the volume group
301       - vg_size: The total size of the volume group (in gibibytes)
302       - vg_free: The available space in the volume group
303
304   """
305   result = ExecCommand("vgs --nohead -o lv_count,vg_size,vg_free"
306                        " --nosuffix --units g"
307                        " --ignorelockingfailure %s" % vgname)
308   if not result.failed:
309     try:
310       lv_count, vg_size, vg_free = result.stdout.strip().split()
311     except ValueError:
312       # This means the output of vgdisplay can't be parsed
313       raise PrereqError("cannot parse output of vgs (%s)" % result.stdout)
314   else:
315     lv_count = vg_size = vg_free = None
316
317   return not result.failed, lv_count, vg_size, vg_free
318
319
320 def CheckSysDev(name, devnum):
321   """Checks consistency between /sys and /dev trees.
322
323   In /sys/block/<name>/dev and /sys/block/<name>/<part>/dev are the
324   kernel-known device numbers. The /dev/<name> block/char devices are
325   created by userspace and thus could differ from the kernel
326   view. This function checks the consistency between the device number
327   read from /sys and the actual device number in /dev.
328
329   Note that since the system could be using udev which removes and
330   recreates the device nodes on partition table rescan, we need to do
331   some retries here. Since we only do a stat, we can afford to do many
332   short retries.
333
334   @param name: the device name, e.g. 'sda'
335   @param devnum: the device number, e.g. 0x803 (2051 in decimal) for sda3
336   @raises L{SysconfigError}: in case of failure of the check
337
338   """
339   path = "/dev/%s" % name
340   for _ in range(40):
341     if os.path.exists(path):
342       break
343     time.sleep(0.250)
344   else:
345     raise SysconfigError("the device file %s does not exist, but the block"
346                          " device exists in the /sys/block tree" % path)
347   rdev = os.stat(path).st_rdev
348   if devnum != rdev:
349     raise SysconfigError("For device %s, the major:minor in /dev is %04x"
350                          " while the major:minor in sysfs is %s" %
351                          (path, rdev, devnum))
352
353
354 def ReadDev(syspath):
355   """Reads the device number from a sysfs path.
356
357   The device number is given in sysfs under a block device directory
358   in a file named 'dev' which contains major:minor (in ASCII). This
359   function reads that file and converts the major:minor pair to a dev
360   number.
361
362   @type syspath: string
363   @param syspath: the path to a block device dir in sysfs,
364       e.g. C{/sys/block/sda}
365
366   @return: the device number
367
368   """
369   if not os.path.exists("%s/dev" % syspath):
370     raise ProgrammingError("Invalid path passed to ReadDev: %s" % syspath)
371   f = open("%s/dev" % syspath)
372   data = f.read().strip()
373   f.close()
374   major, minor = data.split(":", 1)
375   major = int(major)
376   minor = int(minor)
377   dev = os.makedev(major, minor)
378   return dev
379
380
381 def ReadSize(syspath):
382   """Reads the size from a sysfs path.
383
384   The size is given in sysfs under a block device directory in a file
385   named 'size' which contains the number of sectors (in ASCII). This
386   function reads that file and converts the number in sectors to the
387   size in bytes.
388
389   @type syspath: string
390   @param syspath: the path to a block device dir in sysfs,
391       e.g. C{/sys/block/sda}
392
393   @rtype: int
394   @return: the device size in bytes
395
396   """
397
398   if not os.path.exists("%s/size" % syspath):
399     raise ProgrammingError("Invalid path passed to ReadSize: %s" % syspath)
400   f = open("%s/size" % syspath)
401   data = f.read().strip()
402   f.close()
403   size = 512L * int(data)
404   return size
405
406
407 def ReadPV(name):
408   """Reads physical volume information.
409
410   This function tries to see if a block device is a physical volume.
411
412   @type name: string
413   @param name: the device name (e.g. sda)
414
415   @return: the name of the volume group to which this PV belongs, or
416       "" if this PV is not in use, or None if this is not a PV
417
418   """
419   result = ExecCommand("pvdisplay -c /dev/%s" % name)
420   if result.failed:
421     return None
422   vgname = result.stdout.strip().split(":")[1]
423   return vgname
424
425
426 def GetDiskList(opts):
427   """Computes the block device list for this system.
428
429   This function examines the /sys/block tree and using information
430   therein, computes the status of the block device.
431
432   @return: a list like [(name, size, dev, partitions, inuse), ...], where:
433       - name is the block device name (e.g. sda)
434       - size the size in bytes
435       - dev is the device number (e.g. 8704 for hdg)
436       - partitions is [(name, size, dev), ...] mirroring the disk list
437         data inuse is a boolean showing the in-use status of the disk,
438         computed as the possibility of re-reading the partition table
439         (the meaning of the operation varies with the kernel version,
440         but is usually accurate; a mounted disk/partition or swap-area
441         or PV with active LVs on it is busy)
442
443   """
444   dlist = []
445   for name in os.listdir("/sys/block"):
446     if not compat.any([name.startswith(pfx) for pfx in SUPPORTED_TYPES]):
447       continue
448
449     disksysfsname = "/sys/block/%s" % name
450     size = ReadSize(disksysfsname)
451
452     f = open("/sys/block/%s/removable" % name)
453     removable = int(f.read().strip())
454     f.close()
455
456     if removable and not opts.removable_ok:
457       continue
458
459     dev = ReadDev(disksysfsname)
460     CheckSysDev(name, dev)
461     inuse = InUse(name)
462     # Enumerate partitions of the block device
463     partitions = []
464     for partname in os.listdir(disksysfsname):
465       if not partname.startswith(name):
466         continue
467       partsysfsname = "%s/%s" % (disksysfsname, partname)
468       partdev = ReadDev(partsysfsname)
469       partsize = ReadSize(partsysfsname)
470       if partsize >= PART_MINSIZE:
471         CheckSysDev(partname, partdev)
472         partinuse = InUse(partname)
473         partitions.append((partname, partsize, partdev, partinuse))
474     partitions.sort()
475     dlist.append((name, size, dev, partitions, inuse))
476   dlist.sort()
477   return dlist
478
479
480 def GetMountInfo():
481   """Reads /proc/mounts and computes the mountpoint-devnum mapping.
482
483   This function reads /proc/mounts, finds the mounted filesystems
484   (excepting a hard-coded blacklist of network and virtual
485   filesystems) and does a stat on these mountpoints. The st_dev number
486   of the results is memorised for later matching against the
487   /sys/block devices.
488
489   @rtype: dict
490   @return: a {mountpoint: device number} dictionary
491
492   """
493   mountlines = ReadFile("/proc/mounts").splitlines()
494   mounts = {}
495   for line in mountlines:
496     _, mountpoint, fstype, _ = line.split(None, 3)
497     # fs type blacklist
498     if fstype in EXCLUDED_FS:
499       continue
500     try:
501       dev = os.stat(mountpoint).st_dev
502     except OSError, err:
503       # this should be a fairly rare error, since we are blacklisting
504       # network filesystems; with this in mind, we'll ignore it,
505       # since the rereadpt check catches in-use filesystems,
506       # and this is used for disk information only
507       print >> sys.stderr, ("Can't stat mountpoint '%s': %s" %
508                             (mountpoint, err))
509       print >> sys.stderr, "Ignoring."
510       continue
511     mounts[dev] = mountpoint
512   return mounts
513
514
515 def GetSwapInfo():
516   """Reads /proc/swaps and returns the list of swap backing stores.
517
518   """
519   swaplines = ReadFile("/proc/swaps").splitlines()[1:]
520   return [line.split(None, 1)[0] for line in swaplines]
521
522
523 def DevInfo(name, dev, mountinfo):
524   """Computes miscellaneous information about a block device.
525
526   @type name: string
527   @param name: the device name, e.g. sda
528
529   @return: a tuple (mpath, whatvg, fileinfo), where:
530       - mpath is the mount path where this device is mounted or None
531       - whatvg is the result of the ReadPV function
532       - fileinfo is the output of file -bs on the device
533
534   """
535   if dev in mountinfo:
536     mpath = mountinfo[dev]
537   else:
538     mpath = None
539
540   whatvg = ReadPV(name)
541
542   result = ExecCommand("file -bs /dev/%s" % name)
543   if result.failed:
544     fileinfo = "<error: %s>" % result.stderr
545   fileinfo = result.stdout[:45]
546   return mpath, whatvg, fileinfo
547
548
549 def ShowDiskInfo(opts):
550   """Shows a nicely formatted block device list for this system.
551
552   This function shows the user a table with the information gathered
553   by the other functions defined, in order to help the user make a
554   choice about which disks should be allocated to our volume group.
555
556   """
557   def _inuse(inuse):
558     if inuse:
559       return "yes"
560     else:
561       return "no"
562
563   mounts = GetMountInfo()
564   dlist = GetDiskList(opts)
565
566   print "------- Disk information -------"
567   headers = {
568       "name": "Name",
569       "size": "Size[M]",
570       "used": "Used",
571       "mount": "Mount",
572       "lvm": "LVM?",
573       "info": "Info"
574       }
575   fields = ["name", "size", "used", "mount", "lvm", "info"]
576
577   flatlist = []
578   # Flatten the [(disk, [partition,...]), ...] list
579   for name, size, dev, parts, inuse in dlist:
580     flatlist.append((name, size, dev, _inuse(inuse)))
581     for partname, partsize, partdev, partinuse in parts:
582       flatlist.append((partname, partsize, partdev, _inuse(partinuse)))
583
584   strlist = []
585   for name, size, dev, in_use in flatlist:
586     mp, vgname, fileinfo = DevInfo(name, dev, mounts)
587     if mp is None:
588       mp = "-"
589     if vgname is None:
590       lvminfo = "-"
591     elif vgname == "":
592       lvminfo = "yes,free"
593     else:
594       lvminfo = "in %s" % vgname
595
596     if len(name) > 3:
597       # Indent partitions
598       name = " %s" % name
599
600     strlist.append([name, "%.2f" % (float(size) / 1024 / 1024),
601                     in_use, mp, lvminfo, fileinfo])
602
603   data = cli.GenerateTable(headers, fields, None,
604                            strlist, numfields=["size"])
605
606   for line in data:
607     print line
608
609
610 def CheckSysfsHolders(name):
611   """Check to see if a device is 'hold' at sysfs level.
612
613   This is usually the case for Physical Volumes under LVM.
614
615   @rtype: boolean
616   @return: true if the device is available according to sysfs
617
618   """
619   try:
620     contents = os.listdir("%s/holders/" % SysfsName(name))
621   except OSError, err:
622     if err.errno == errno.ENOENT:
623       contents = []
624     else:
625       raise
626   return not bool(contents)
627
628
629 def CheckReread(name):
630   """Check to see if a block device is in use.
631
632   Uses blockdev to reread the partition table of a block device (or
633   fuser if the device is not partitionable), and thus compute the
634   in-use status.  See the discussion in GetDiskList about the meaning
635   of 'in use'.
636
637   @rtype: boolean
638   @return: the in-use status of the device
639
640   """
641   use_blockdev = IsPartitioned(name)
642   if use_blockdev:
643     cmd = "blockdev --rereadpt /dev/%s" % name
644   else:
645     cmd = "fuser -vam /dev/%s" % name
646
647   for _ in range(3):
648     result = ExecCommand(cmd)
649     if not use_blockdev and result.failed:
650       break
651     elif not result.failed:
652       break
653     time.sleep(2)
654
655   if use_blockdev:
656     return not result.failed
657   else:
658     return result.failed
659
660
661 def CheckMounted(name):
662   """Check to see if a block device is a mountpoint.
663
664   In recent distros/kernels, this is reported directly via fuser, but
665   on older ones not, so we do an additional check here (manually).
666
667   """
668   minfo = GetMountInfo()
669   dev = ReadDev(SysfsName(name))
670   return dev not in minfo
671
672
673 def CheckSwap(name):
674   """Check to see if a block device is being used as swap.
675
676   """
677   name = "/dev/%s" % name
678   return name not in GetSwapInfo()
679
680
681 def InUse(name):
682   """Returns if a disk is in use or not.
683
684   """
685   return not (CheckSysfsHolders(name) and CheckReread(name) and
686               CheckMounted(name) and CheckSwap(name))
687
688
689 def WipeDisk(name):
690   """Wipes a block device.
691
692   This function wipes a block device, by clearing and re-reading the
693   partition table. If not successful, it writes back the old partition
694   data, and leaves the cleanup to the user.
695
696   @param name: the device name (e.g. sda)
697
698   """
699
700   if InUse(name):
701     raise OperationalError("CRITICAL: disk %s you selected seems to be in"
702                            " use. ABORTING!" % name)
703
704   fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
705   olddata = os.read(fd, 512)
706   if len(olddata) != 512:
707     raise OperationalError("CRITICAL: Can't read partition table information"
708                            " from /dev/%s (needed 512 bytes, got %d" %
709                            (name, len(olddata)))
710   newdata = "\0" * 512
711   os.lseek(fd, 0, 0)
712   bytes_written = os.write(fd, newdata)
713   os.close(fd)
714   if bytes_written != 512:
715     raise OperationalError("CRITICAL: Can't write partition table information"
716                            " to /dev/%s (tried to write 512 bytes, written"
717                            " %d. I don't know how to cleanup. Sorry." %
718                            (name, bytes_written))
719
720   if InUse(name):
721     # try to restore the data
722     fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
723     os.write(fd, olddata)
724     os.close(fd)
725     raise OperationalError("CRITICAL: disk %s which I have just wiped cannot"
726                            " reread partition table. Most likely, it is"
727                            " in use. You have to clean after this yourself."
728                            " I tried to restore the old partition table,"
729                            " but I cannot guarantee nothing has broken." %
730                            name)
731
732
733 def PartitionDisk(name):
734   """Partitions a disk.
735
736   This function creates a single partition spanning the entire disk,
737   by means of fdisk.
738
739   @param name: the device name, e.g. sda
740
741   """
742   result = ExecCommand(
743     'echo ,,8e, | sfdisk /dev/%s' % name)
744   if result.failed:
745     raise OperationalError("CRITICAL: disk %s which I have just partitioned"
746                            " cannot reread its partition table, or there"
747                            " is some other sfdisk error. Likely, it is in"
748                            " use. You have to clean this yourself. Error"
749                            " message from sfdisk: %s" %
750                            (name, result.output))
751
752
753 def CreatePVOnDisk(name):
754   """Creates a physical volume on a block device.
755
756   This function creates a physical volume on a block device, overriding
757   all warnings. So it can wipe existing PVs and PVs which are in a VG.
758
759   @param name: the device name, e.g. sda
760
761   """
762   device = DeviceName(name)
763   result = ExecCommand("pvcreate -yff %s" % device)
764   if result.failed:
765     raise OperationalError("I cannot create a physical volume on"
766                            " %s. Error message: %s."
767                            " Please clean up yourself." %
768                            (device, result.output))
769
770
771 def CreateVG(vgname, disks):
772   """Creates the volume group.
773
774   This function creates a volume group named `vgname` on the disks
775   given as parameters. The physical extent size is set to 64MB.
776
777   @param disks: a list of disk names, e.g. ['sda','sdb']
778
779   """
780   pnames = [DeviceName(d) for d in disks]
781   result = ExecCommand("vgcreate -s 64MB '%s' %s" % (vgname, " ".join(pnames)))
782   if result.failed:
783     raise OperationalError("I cannot create the volume group %s from"
784                            " disks %s. Error message: %s. Please clean up"
785                            " yourself." %
786                            (vgname, " ".join(disks), result.output))
787
788
789 def ValidateDiskList(options):
790   """Validates or computes the disk list for create.
791
792   This function either computes the available disk list (if the user
793   gave --alldisks option), or validates the user-given disk list (by
794   using the --disks option) such that all given disks are present and
795   not in use.
796
797   @param options: the options returned from OptParser.parse_options
798
799   @return: a list of disk names, e.g. ['sda', 'sdb']
800
801   """
802   sysdisks = GetDiskList(options)
803   if not sysdisks:
804     raise PrereqError("no disks found (I looked for"
805                       " non-removable block devices).")
806   sysd_free = []
807   sysd_used = []
808   for name, _, _, parts, used in sysdisks:
809     if used:
810       sysd_used.append(name)
811       for partname, _, _, partused in parts:
812         if partused:
813           sysd_used.append(partname)
814         else:
815           sysd_free.append(partname)
816     else:
817       sysd_free.append(name)
818
819   if not sysd_free:
820     raise PrereqError("no free disks found! (%d in-use disks)" %
821                       len(sysd_used))
822   if options.alldisks:
823     disklist = sysd_free
824   elif options.disks:
825     disklist = options.disks.split(",")
826     for name in disklist:
827       if name in sysd_used:
828         raise ParameterError("disk %s is in use, cannot wipe!" % name)
829       if name not in sysd_free:
830         raise ParameterError("cannot find disk %s!" % name)
831   else:
832     raise ParameterError("Please use either --alldisks or --disks!")
833
834   return disklist
835
836
837 def BootStrap():
838   """Actual main routine.
839
840   """
841   CheckPrereq()
842
843   options, args = ParseOptions()
844   vgname = options.vgname
845   command = args.pop(0)
846   if command == "diskinfo":
847     ShowDiskInfo(options)
848     return
849   if command != "create":
850     Usage()
851
852   exists, lv_count, vg_size, vg_free = CheckVGExists(vgname)
853   if exists:
854     raise PrereqError("It seems volume group '%s' already exists:\n"
855                       "  LV count: %s, size: %s, free: %s." %
856                       (vgname, lv_count, vg_size, vg_free))
857
858
859   disklist = ValidateDiskList(options)
860
861   for disk in disklist:
862     WipeDisk(disk)
863     if IsPartitioned(disk):
864       PartitionDisk(disk)
865   for disk in disklist:
866     CreatePVOnDisk(disk)
867   CreateVG(vgname, disklist)
868
869   status, lv_count, size, _ = CheckVGExists(vgname)
870   if status:
871     print "Done! %s: size %s GiB, disks: %s" % (vgname, size,
872                                               ",".join(disklist))
873   else:
874     raise OperationalError("Although everything seemed ok, the volume"
875                            " group did not get created.")
876
877
878 def main():
879   """Application entry point.
880
881   This is just a wrapper over BootStrap, to handle our own exceptions.
882
883   """
884   try:
885     BootStrap()
886   except PrereqError, err:
887     print >> sys.stderr, "The prerequisites for running this tool are not met."
888     print >> sys.stderr, ("Please make sure you followed all the steps in"
889                           " the build document.")
890     print >> sys.stderr, "Description: %s" % str(err)
891     sys.exit(1)
892   except SysconfigError, err:
893     print >> sys.stderr, ("This system's configuration seems wrong, at"
894                           " least is not what I expect.")
895     print >> sys.stderr, ("Please check that the installation didn't fail"
896                           " at some step.")
897     print >> sys.stderr, "Description: %s" % str(err)
898     sys.exit(1)
899   except ParameterError, err:
900     print >> sys.stderr, ("Some parameters you gave to the program or the"
901                           " invocation is wrong. ")
902     print >> sys.stderr, "Description: %s" % str(err)
903     Usage()
904   except OperationalError, err:
905     print >> sys.stderr, ("A serious error has happened while modifying"
906                           " the system's configuration.")
907     print >> sys.stderr, ("Please review the error message below and make"
908                           " sure you clean up yourself.")
909     print >> sys.stderr, ("It is most likely that the system configuration"
910                           " has been partially altered.")
911     print >> sys.stderr, str(err)
912     sys.exit(1)
913   except ProgrammingError, err:
914     print >> sys.stderr, ("Internal application error. Please report this"
915                           " to the Ganeti developer list.")
916     print >> sys.stderr, "Error description: %s" % str(err)
917     sys.exit(1)
918   except Error, err:
919     print >> sys.stderr, "Unhandled application error: %s" % err
920     sys.exit(1)
921   except (IOError, OSError), err:
922     print >> sys.stderr, "I/O error detected, please report."
923     print >> sys.stderr, "Description: %s" % str(err)
924     sys.exit(1)
925
926
927 if __name__ == "__main__":
928   main()