Statistics
| Branch: | Tag: | Revision:

root / tools / lvmstrap @ 6714256c

History | View | Annotate | Download (25.5 kB)

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

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

    
21

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

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

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

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

    
40
"""
41

    
42

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

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

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

    
59
verbose_flag = False
60

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

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

    
81

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

    
86

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

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

    
93
  """
94
  pass
95

    
96

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

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

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

    
107
  """
108
  pass
109

    
110

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

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

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

    
121
  """
122
  pass
123

    
124

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

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

    
130
  """
131
  pass
132

    
133

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

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

    
140
  """
141
  pass
142

    
143

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

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

    
152

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

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

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

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

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

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

    
184

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

    
189
  verbose_flag = options.verbose
190

    
191
  return options, args
192

    
193

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

    
197
  Currently only md devices are used as is.
198

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

    
202

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

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

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

    
216

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

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

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

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

    
237

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

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

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

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

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

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

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

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

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

    
272

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

    
276
  @param vgname: the volume group name
277

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

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

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

    
300

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

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

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

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

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

    
334

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

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

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

    
347
  @return: the device number
348

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

    
361

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

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

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

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

    
377
  """
378

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

    
387

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

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

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

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

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

    
406

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

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

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

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

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

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

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

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

    
456

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

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

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

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

    
491

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

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

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

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

    
509
  whatvg = ReadPV(name)
510

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

    
517

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

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

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

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

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

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

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

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

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

    
573
  for line in data:
574
    print line
575

    
576

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

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

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

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

    
595

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

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

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

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

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

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

    
627

    
628
def InUse(name):
629
  """Returns if a disk is in use or not.
630

    
631
  """
632
  return not (CheckSysfsHolders(name) and CheckReread(name))
633

    
634

    
635
def WipeDisk(name):
636
  """Wipes a block device.
637

    
638
  This function wipes a block device, by clearing and re-reading the
639
  partition table. If not successful, it writes back the old partition
640
  data, and leaves the cleanup to the user.
641

    
642
  @param name: the device name (e.g. sda)
643

    
644
  """
645

    
646
  if InUse(name):
647
    raise OperationalError("CRITICAL: disk %s you selected seems to be in"
648
                           " use. ABORTING!" % name)
649

    
650
  fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
651
  olddata = os.read(fd, 512)
652
  if len(olddata) != 512:
653
    raise OperationalError("CRITICAL: Can't read partition table information"
654
                           " from /dev/%s (needed 512 bytes, got %d" %
655
                           (name, len(olddata)))
656
  newdata = "\0" * 512
657
  os.lseek(fd, 0, 0)
658
  bytes_written = os.write(fd, newdata)
659
  os.close(fd)
660
  if bytes_written != 512:
661
    raise OperationalError("CRITICAL: Can't write partition table information"
662
                           " to /dev/%s (tried to write 512 bytes, written"
663
                           " %d. I don't know how to cleanup. Sorry." %
664
                           (name, bytes_written))
665

    
666
  if InUse(name):
667
    # try to restore the data
668
    fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
669
    os.write(fd, olddata)
670
    os.close(fd)
671
    raise OperationalError("CRITICAL: disk %s which I have just wiped cannot"
672
                           " reread partition table. Most likely, it is"
673
                           " in use. You have to clean after this yourself."
674
                           " I tried to restore the old partition table,"
675
                           " but I cannot guarantee nothing has broken." %
676
                           name)
677

    
678

    
679
def PartitionDisk(name):
680
  """Partitions a disk.
681

    
682
  This function creates a single partition spanning the entire disk,
683
  by means of fdisk.
684

    
685
  @param name: the device name, e.g. sda
686

    
687
  """
688
  result = ExecCommand(
689
    'echo ,,8e, | sfdisk /dev/%s' % name)
690
  if result.failed:
691
    raise OperationalError("CRITICAL: disk %s which I have just partitioned"
692
                           " cannot reread its partition table, or there"
693
                           " is some other sfdisk error. Likely, it is in"
694
                           " use. You have to clean this yourself. Error"
695
                           " message from sfdisk: %s" %
696
                           (name, result.output))
697

    
698

    
699
def CreatePVOnDisk(name):
700
  """Creates a physical volume on a block device.
701

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

    
705
  @param name: the device name, e.g. sda
706

    
707
  """
708
  device = DeviceName(name)
709
  result = ExecCommand("pvcreate -yff %s" % device)
710
  if result.failed:
711
    raise OperationalError("I cannot create a physical volume on"
712
                           " %s. Error message: %s."
713
                           " Please clean up yourself." %
714
                           (device, result.output))
715

    
716

    
717
def CreateVG(vgname, disks):
718
  """Creates the volume group.
719

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

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

    
725
  """
726
  pnames = [DeviceName(d) for d in disks]
727
  result = ExecCommand("vgcreate -s 64MB '%s' %s" % (vgname, " ".join(pnames)))
728
  if result.failed:
729
    raise OperationalError("I cannot create the volume group %s from"
730
                           " disks %s. Error message: %s. Please clean up"
731
                           " yourself." %
732
                           (vgname, " ".join(disks), result.output))
733

    
734

    
735
def ValidateDiskList(options):
736
  """Validates or computes the disk list for create.
737

    
738
  This function either computes the available disk list (if the user
739
  gave --alldisks option), or validates the user-given disk list (by
740
  using the --disks option) such that all given disks are present and
741
  not in use.
742

    
743
  @param options: the options returned from OptParser.parse_options
744

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

    
747
  """
748
  sysdisks = GetDiskList(options)
749
  if not sysdisks:
750
    raise PrereqError("no disks found (I looked for"
751
                      " non-removable block devices).")
752
  sysd_free = []
753
  sysd_used = []
754
  for name, _, _, _, used in sysdisks:
755
    if used:
756
      sysd_used.append(name)
757
    else:
758
      sysd_free.append(name)
759

    
760
  if not sysd_free:
761
    raise PrereqError("no free disks found! (%d in-use disks)" %
762
                      len(sysd_used))
763
  if options.alldisks:
764
    disklist = sysd_free
765
  elif options.disks:
766
    disklist = options.disks.split(",")
767
    for name in disklist:
768
      if name in sysd_used:
769
        raise ParameterError("disk %s is in use, cannot wipe!" % name)
770
      if name not in sysd_free:
771
        raise ParameterError("cannot find disk %s!" % name)
772
  else:
773
    raise ParameterError("Please use either --alldisks or --disks!")
774

    
775
  return disklist
776

    
777

    
778
def BootStrap():
779
  """Actual main routine.
780

    
781
  """
782
  CheckPrereq()
783

    
784
  options, args = ParseOptions()
785
  vgname = options.vgname
786
  command = args.pop(0)
787
  if command == "diskinfo":
788
    ShowDiskInfo(options)
789
    return
790
  if command != "create":
791
    Usage()
792

    
793
  exists, lv_count, vg_size, vg_free = CheckVGExists(vgname)
794
  if exists:
795
    raise PrereqError("It seems volume group '%s' already exists:\n"
796
                      "  LV count: %s, size: %s, free: %s." %
797
                      (vgname, lv_count, vg_size, vg_free))
798

    
799

    
800
  disklist = ValidateDiskList(options)
801

    
802
  for disk in disklist:
803
    WipeDisk(disk)
804
    if IsPartitioned(disk):
805
      PartitionDisk(disk)
806
  for disk in disklist:
807
    CreatePVOnDisk(disk)
808
  CreateVG(vgname, disklist)
809

    
810
  status, lv_count, size, _ = CheckVGExists(vgname)
811
  if status:
812
    print "Done! %s: size %s GiB, disks: %s" % (vgname, size,
813
                                              ",".join(disklist))
814
  else:
815
    raise OperationalError("Although everything seemed ok, the volume"
816
                           " group did not get created.")
817

    
818

    
819
def main():
820
  """Application entry point.
821

    
822
  This is just a wrapper over BootStrap, to handle our own exceptions.
823

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

    
867

    
868
if __name__ == "__main__":
869
  main()