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