Statistics
| Branch: | Tag: | Revision:

root / tools / lvmstrap @ add478b5

History | View | Annotate | Download (26.3 kB)

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

    
4
# Copyright (C) 2006, 2007, 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Program which configures LVM on the Ganeti nodes.
23

    
24
This program wipes disks and creates a volume group on top of them. It
25
can also show disk information to help you decide which disks you want
26
to wipe.
27

    
28
The error handling is done by raising our own exceptions from most of
29
the functions; these exceptions then handled globally in the main()
30
function. The exceptions that each function can raise are not
31
documented individually, since almost every error path ends in a
32
raise.
33

    
34
Another two exceptions that are handled globally are IOError and
35
OSError. The idea behind this is, since we run as root, we should
36
usually not get these errors, but if we do it's most probably a system
37
error, so they should be handled and the user instructed to report
38
them.
39

    
40
"""
41

    
42

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

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

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

    
59
verbose_flag = False
60

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

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

    
81
#: Minimum partition size to be considered (1 GB)
82
PART_MINSIZE = 1024 * 1024 * 1024
83

    
84

    
85
class Error(Exception):
86
  """Generic exception"""
87
  pass
88

    
89

    
90
class ProgrammingError(Error):
91
  """Exception denoting invalid assumptions in programming.
92

    
93
  This should catch sysfs tree changes, or otherwise incorrect
94
  assumptions about the contents of the /sys/block/... directories.
95

    
96
  """
97
  pass
98

    
99

    
100
class SysconfigError(Error):
101
  """Exception denoting invalid system configuration.
102

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

    
107
  This should usually mean that the installation of the Xen node
108
  failed in some steps.
109

    
110
  """
111
  pass
112

    
113

    
114
class PrereqError(Error):
115
  """Exception denoting invalid prerequisites.
116

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

    
121
  This should usually mean that the build steps for the Xen node were
122
  not followed correctly.
123

    
124
  """
125
  pass
126

    
127

    
128
class OperationalError(Error):
129
  """Exception denoting actual errors.
130

    
131
  Errors during the bootstrapping are signaled using this exception.
132

    
133
  """
134
  pass
135

    
136

    
137
class ParameterError(Error):
138
  """Exception denoting invalid input from user.
139

    
140
  Wrong disks given as parameters will be signaled using this
141
  exception.
142

    
143
  """
144
  pass
145

    
146

    
147
def Usage():
148
  """Shows program usage information and exits the program.
149

    
150
  """
151
  print >> sys.stderr, "Usage:"
152
  print >> sys.stderr, USAGE
153
  sys.exit(2)
154

    
155

    
156
def ParseOptions():
157
  """Parses the command line options.
158

    
159
  In case of command line errors, it will show the usage and exit the
160
  program.
161

    
162
  @rtype: tuple
163
  @return: a tuple of (options, args), as returned by
164
      OptionParser.parse_args
165

    
166
  """
167
  global verbose_flag # pylint: disable-msg=W0603
168

    
169
  parser = optparse.OptionParser(usage="\n%s" % USAGE,
170
                                 version="%%prog (ganeti) %s" %
171
                                 constants.RELEASE_VERSION)
172

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

    
187

    
188
  options, args = parser.parse_args()
189
  if len(args) != 1:
190
    Usage()
191

    
192
  verbose_flag = options.verbose
193

    
194
  return options, args
195

    
196

    
197
def IsPartitioned(disk):
198
  """Returns whether a given disk should be used partitioned or as-is.
199

    
200
  Currently only md devices are used as is.
201

    
202
  """
203
  return not disk.startswith('md')
204

    
205

    
206
def DeviceName(disk):
207
  """Returns the appropriate device name for a disk.
208

    
209
  For non-partitioned devices, it returns the name as is, otherwise it
210
  returns the first partition.
211

    
212
  """
213
  if IsPartitioned(disk):
214
    device = '/dev/%s1' % disk
215
  else:
216
    device = '/dev/%s' % disk
217
  return device
218

    
219

    
220
def ExecCommand(command):
221
  """Executes a command.
222

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

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

    
232
  """
233
  if verbose_flag:
234
    print command
235
  result = RunCmd(command)
236
  if verbose_flag:
237
    print result.output
238
  return result
239

    
240

    
241
def CheckPrereq():
242
  """Check the prerequisites of this program.
243

    
244
  It check that it runs on Linux 2.6, and that /sys is mounted and the
245
  fact that /sys/block is a directory.
246

    
247
  """
248
  if os.getuid() != 0:
249
    raise PrereqError("This tool runs as root only. Really.")
250

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

    
256
  if not release.startswith("2.6."):
257
    raise PrereqError("Wrong major kernel version (detected %s, needs"
258
                      " 2.6.*)" % release)
259

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

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

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

    
272
  if not os.path.exists("/proc/mounts"):
273
    raise SysconfigError("Can't find /proc/mounts")
274

    
275

    
276
def CheckVGExists(vgname):
277
  """Checks to see if a volume group exists.
278

    
279
  @param vgname: the volume group name
280

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

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

    
301
  return not result.failed, lv_count, vg_size, vg_free
302

    
303

    
304
def CheckSysDev(name, devnum):
305
  """Checks consistency between /sys and /dev trees.
306

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

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

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

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

    
337

    
338
def ReadDev(syspath):
339
  """Reads the device number from a sysfs path.
340

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

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

    
350
  @return: the device number
351

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

    
364

    
365
def ReadSize(syspath):
366
  """Reads the size from a sysfs path.
367

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

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

    
377
  @rtype: int
378
  @return: the device size in bytes
379

    
380
  """
381

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

    
390

    
391
def ReadPV(name):
392
  """Reads physical volume information.
393

    
394
  This function tries to see if a block device is a physical volume.
395

    
396
  @type name: string
397
  @param name: the device name (e.g. sda)
398

    
399
  @return: the name of the volume group to which this PV belongs, or
400
      "" if this PV is not in use, or None if this is not a PV
401

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

    
409

    
410
def GetDiskList(opts):
411
  """Computes the block device list for this system.
412

    
413
  This function examines the /sys/block tree and using information
414
  therein, computes the status of the block device.
415

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

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

    
433
    size = ReadSize("/sys/block/%s" % name)
434

    
435
    f = open("/sys/block/%s/removable" % name)
436
    removable = int(f.read().strip())
437
    f.close()
438

    
439
    if removable and not opts.removable_ok:
440
      continue
441

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

    
460

    
461
def GetMountInfo():
462
  """Reads /proc/mounts and computes the mountpoint-devnum mapping.
463

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

    
470
  @rtype: dict
471
  @return: a {mountpoint: device number} dictionary
472

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

    
495

    
496
def GetSwapInfo():
497
  """Reads /proc/swaps and returns the list of swap backing stores.
498

    
499
  """
500
  swaplines = ReadFile("/proc/swaps").splitlines()[1:]
501
  return [line.split(None, 1)[0] for line in swaplines]
502

    
503

    
504
def DevInfo(name, dev, mountinfo):
505
  """Computes miscellaneous information about a block device.
506

    
507
  @type name: string
508
  @param name: the device name, e.g. sda
509

    
510
  @return: a tuple (mpath, whatvg, fileinfo), where:
511
      - mpath is the mount path where this device is mounted or None
512
      - whatvg is the result of the ReadPV function
513
      - fileinfo is the output of file -bs on the device
514

    
515
  """
516
  if dev in mountinfo:
517
    mpath = mountinfo[dev]
518
  else:
519
    mpath = None
520

    
521
  whatvg = ReadPV(name)
522

    
523
  result = ExecCommand("file -bs /dev/%s" % name)
524
  if result.failed:
525
    fileinfo = "<error: %s>" % result.stderr
526
  fileinfo = result.stdout[:45]
527
  return mpath, whatvg, fileinfo
528

    
529

    
530
def ShowDiskInfo(opts):
531
  """Shows a nicely formatted block device list for this system.
532

    
533
  This function shows the user a table with the information gathered
534
  by the other functions defined, in order to help the user make a
535
  choice about which disks should be allocated to our volume group.
536

    
537
  """
538
  mounts = GetMountInfo()
539
  dlist = GetDiskList(opts)
540

    
541
  print "------- Disk information -------"
542
  headers = {
543
      "name": "Name",
544
      "size": "Size[M]",
545
      "used": "Used",
546
      "mount": "Mount",
547
      "lvm": "LVM?",
548
      "info": "Info"
549
      }
550
  fields = ["name", "size", "used", "mount", "lvm", "info"]
551

    
552
  flatlist = []
553
  # Flatten the [(disk, [partition,...]), ...] list
554
  for name, size, dev, parts, inuse in dlist:
555
    if inuse:
556
      str_inuse = "yes"
557
    else:
558
      str_inuse = "no"
559
    flatlist.append((name, size, dev, str_inuse))
560
    for partname, partsize, partdev in parts:
561
      flatlist.append((partname, partsize, partdev, ""))
562

    
563
  strlist = []
564
  for name, size, dev, in_use in flatlist:
565
    mp, vgname, fileinfo = DevInfo(name, dev, mounts)
566
    if mp is None:
567
      mp = "-"
568
    if vgname is None:
569
      lvminfo = "-"
570
    elif vgname == "":
571
      lvminfo = "yes,free"
572
    else:
573
      lvminfo = "in %s" % vgname
574

    
575
    if len(name) > 3:
576
      # Indent partitions
577
      name = " %s" % name
578

    
579
    strlist.append([name, "%.2f" % (float(size) / 1024 / 1024),
580
                    in_use, mp, lvminfo, fileinfo])
581

    
582
  data = cli.GenerateTable(headers, fields, None,
583
                           strlist, numfields=["size"])
584

    
585
  for line in data:
586
    print line
587

    
588

    
589
def CheckSysfsHolders(name):
590
  """Check to see if a device is 'hold' at sysfs level.
591

    
592
  This is usually the case for Physical Volumes under LVM.
593

    
594
  @rtype: boolean
595
  @return: true if the device is available according to sysfs
596

    
597
  """
598
  try:
599
    contents = os.listdir("/sys/block/%s/holders/" % name)
600
  except OSError, err:
601
    if err.errno == errno.ENOENT:
602
      contents = []
603
    else:
604
      raise
605
  return not bool(contents)
606

    
607

    
608
def CheckReread(name):
609
  """Check to see if a block device is in use.
610

    
611
  Uses blockdev to reread the partition table of a block device (or
612
  fuser if the device is not partitionable), and thus compute the
613
  in-use status.  See the discussion in GetDiskList about the meaning
614
  of 'in use'.
615

    
616
  @rtype: boolean
617
  @return: the in-use status of the device
618

    
619
  """
620
  use_blockdev = IsPartitioned(name)
621
  if use_blockdev:
622
    cmd = "blockdev --rereadpt /dev/%s" % name
623
  else:
624
    cmd = "fuser -vam /dev/%s" % name
625

    
626
  for _ in range(3):
627
    result = ExecCommand(cmd)
628
    if not use_blockdev and result.failed:
629
      break
630
    elif not result.failed:
631
      break
632
    time.sleep(2)
633

    
634
  if use_blockdev:
635
    return not result.failed
636
  else:
637
    return result.failed
638

    
639

    
640
def CheckMounted(name):
641
  """Check to see if a block device is a mountpoint.
642

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

    
646
  """
647
  minfo = GetMountInfo()
648
  dev = ReadDev("/sys/block/%s" % name)
649
  return dev not in minfo
650

    
651

    
652
def CheckSwap(name):
653
  """Check to see if a block device is being used as swap.
654

    
655
  """
656
  name = "/dev/%s" % name
657
  return name not in GetSwapInfo()
658

    
659

    
660
def InUse(name):
661
  """Returns if a disk is in use or not.
662

    
663
  """
664
  return not (CheckSysfsHolders(name) and CheckReread(name) and
665
              CheckMounted(name) and CheckSwap(name))
666

    
667

    
668
def WipeDisk(name):
669
  """Wipes a block device.
670

    
671
  This function wipes a block device, by clearing and re-reading the
672
  partition table. If not successful, it writes back the old partition
673
  data, and leaves the cleanup to the user.
674

    
675
  @param name: the device name (e.g. sda)
676

    
677
  """
678

    
679
  if InUse(name):
680
    raise OperationalError("CRITICAL: disk %s you selected seems to be in"
681
                           " use. ABORTING!" % name)
682

    
683
  fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
684
  olddata = os.read(fd, 512)
685
  if len(olddata) != 512:
686
    raise OperationalError("CRITICAL: Can't read partition table information"
687
                           " from /dev/%s (needed 512 bytes, got %d" %
688
                           (name, len(olddata)))
689
  newdata = "\0" * 512
690
  os.lseek(fd, 0, 0)
691
  bytes_written = os.write(fd, newdata)
692
  os.close(fd)
693
  if bytes_written != 512:
694
    raise OperationalError("CRITICAL: Can't write partition table information"
695
                           " to /dev/%s (tried to write 512 bytes, written"
696
                           " %d. I don't know how to cleanup. Sorry." %
697
                           (name, bytes_written))
698

    
699
  if InUse(name):
700
    # try to restore the data
701
    fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
702
    os.write(fd, olddata)
703
    os.close(fd)
704
    raise OperationalError("CRITICAL: disk %s which I have just wiped cannot"
705
                           " reread partition table. Most likely, it is"
706
                           " in use. You have to clean after this yourself."
707
                           " I tried to restore the old partition table,"
708
                           " but I cannot guarantee nothing has broken." %
709
                           name)
710

    
711

    
712
def PartitionDisk(name):
713
  """Partitions a disk.
714

    
715
  This function creates a single partition spanning the entire disk,
716
  by means of fdisk.
717

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

    
720
  """
721
  result = ExecCommand(
722
    'echo ,,8e, | sfdisk /dev/%s' % name)
723
  if result.failed:
724
    raise OperationalError("CRITICAL: disk %s which I have just partitioned"
725
                           " cannot reread its partition table, or there"
726
                           " is some other sfdisk error. Likely, it is in"
727
                           " use. You have to clean this yourself. Error"
728
                           " message from sfdisk: %s" %
729
                           (name, result.output))
730

    
731

    
732
def CreatePVOnDisk(name):
733
  """Creates a physical volume on a block device.
734

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

    
738
  @param name: the device name, e.g. sda
739

    
740
  """
741
  device = DeviceName(name)
742
  result = ExecCommand("pvcreate -yff %s" % device)
743
  if result.failed:
744
    raise OperationalError("I cannot create a physical volume on"
745
                           " %s. Error message: %s."
746
                           " Please clean up yourself." %
747
                           (device, result.output))
748

    
749

    
750
def CreateVG(vgname, disks):
751
  """Creates the volume group.
752

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

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

    
758
  """
759
  pnames = [DeviceName(d) for d in disks]
760
  result = ExecCommand("vgcreate -s 64MB '%s' %s" % (vgname, " ".join(pnames)))
761
  if result.failed:
762
    raise OperationalError("I cannot create the volume group %s from"
763
                           " disks %s. Error message: %s. Please clean up"
764
                           " yourself." %
765
                           (vgname, " ".join(disks), result.output))
766

    
767

    
768
def ValidateDiskList(options):
769
  """Validates or computes the disk list for create.
770

    
771
  This function either computes the available disk list (if the user
772
  gave --alldisks option), or validates the user-given disk list (by
773
  using the --disks option) such that all given disks are present and
774
  not in use.
775

    
776
  @param options: the options returned from OptParser.parse_options
777

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

    
780
  """
781
  sysdisks = GetDiskList(options)
782
  if not sysdisks:
783
    raise PrereqError("no disks found (I looked for"
784
                      " non-removable block devices).")
785
  sysd_free = []
786
  sysd_used = []
787
  for name, _, _, _, used in sysdisks:
788
    if used:
789
      sysd_used.append(name)
790
    else:
791
      sysd_free.append(name)
792

    
793
  if not sysd_free:
794
    raise PrereqError("no free disks found! (%d in-use disks)" %
795
                      len(sysd_used))
796
  if options.alldisks:
797
    disklist = sysd_free
798
  elif options.disks:
799
    disklist = options.disks.split(",")
800
    for name in disklist:
801
      if name in sysd_used:
802
        raise ParameterError("disk %s is in use, cannot wipe!" % name)
803
      if name not in sysd_free:
804
        raise ParameterError("cannot find disk %s!" % name)
805
  else:
806
    raise ParameterError("Please use either --alldisks or --disks!")
807

    
808
  return disklist
809

    
810

    
811
def BootStrap():
812
  """Actual main routine.
813

    
814
  """
815
  CheckPrereq()
816

    
817
  options, args = ParseOptions()
818
  vgname = options.vgname
819
  command = args.pop(0)
820
  if command == "diskinfo":
821
    ShowDiskInfo(options)
822
    return
823
  if command != "create":
824
    Usage()
825

    
826
  exists, lv_count, vg_size, vg_free = CheckVGExists(vgname)
827
  if exists:
828
    raise PrereqError("It seems volume group '%s' already exists:\n"
829
                      "  LV count: %s, size: %s, free: %s." %
830
                      (vgname, lv_count, vg_size, vg_free))
831

    
832

    
833
  disklist = ValidateDiskList(options)
834

    
835
  for disk in disklist:
836
    WipeDisk(disk)
837
    if IsPartitioned(disk):
838
      PartitionDisk(disk)
839
  for disk in disklist:
840
    CreatePVOnDisk(disk)
841
  CreateVG(vgname, disklist)
842

    
843
  status, lv_count, size, _ = CheckVGExists(vgname)
844
  if status:
845
    print "Done! %s: size %s GiB, disks: %s" % (vgname, size,
846
                                              ",".join(disklist))
847
  else:
848
    raise OperationalError("Although everything seemed ok, the volume"
849
                           " group did not get created.")
850

    
851

    
852
def main():
853
  """Application entry point.
854

    
855
  This is just a wrapper over BootStrap, to handle our own exceptions.
856

    
857
  """
858
  try:
859
    BootStrap()
860
  except PrereqError, err:
861
    print >> sys.stderr, "The prerequisites for running this tool are not met."
862
    print >> sys.stderr, ("Please make sure you followed all the steps in"
863
                          " the build document.")
864
    print >> sys.stderr, "Description: %s" % str(err)
865
    sys.exit(1)
866
  except SysconfigError, err:
867
    print >> sys.stderr, ("This system's configuration seems wrong, at"
868
                          " least is not what I expect.")
869
    print >> sys.stderr, ("Please check that the installation didn't fail"
870
                          " at some step.")
871
    print >> sys.stderr, "Description: %s" % str(err)
872
    sys.exit(1)
873
  except ParameterError, err:
874
    print >> sys.stderr, ("Some parameters you gave to the program or the"
875
                          " invocation is wrong. ")
876
    print >> sys.stderr, "Description: %s" % str(err)
877
    Usage()
878
  except OperationalError, err:
879
    print >> sys.stderr, ("A serious error has happened while modifying"
880
                          " the system's configuration.")
881
    print >> sys.stderr, ("Please review the error message below and make"
882
                          " sure you clean up yourself.")
883
    print >> sys.stderr, ("It is most likely that the system configuration"
884
                          " has been partially altered.")
885
    print >> sys.stderr, str(err)
886
    sys.exit(1)
887
  except ProgrammingError, err:
888
    print >> sys.stderr, ("Internal application error. Please report this"
889
                          " to the Ganeti developer list.")
890
    print >> sys.stderr, "Error description: %s" % str(err)
891
    sys.exit(1)
892
  except Error, err:
893
    print >> sys.stderr, "Unhandled application error: %s" % err
894
    sys.exit(1)
895
  except (IOError, OSError), err:
896
    print >> sys.stderr, "I/O error detected, please report."
897
    print >> sys.stderr, "Description: %s" % str(err)
898
    sys.exit(1)
899

    
900

    
901
if __name__ == "__main__":
902
  main()