Statistics
| Branch: | Tag: | Revision:

root / tools / lvmstrap @ 1fa6fcba

History | View | Annotate | Download (28.6 kB)

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 = compat.UniqueFrozenset([
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()