Statistics
| Branch: | Tag: | Revision:

root / tools / lvmstrap @ 7706fdd4

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
    disksysfsname = "/sys/block/%s" % name
434
    size = ReadSize(disksysfsname)
435

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

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

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

    
462

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

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

    
472
  @rtype: dict
473
  @return: a {mountpoint: device number} dictionary
474

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

    
497

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

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

    
505

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

    
509
  @type name: string
510
  @param name: the device name, e.g. sda
511

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

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

    
523
  whatvg = ReadPV(name)
524

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

    
531

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

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

    
539
  """
540
  mounts = GetMountInfo()
541
  dlist = GetDiskList(opts)
542

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

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

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

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

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

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

    
587
  for line in data:
588
    print line
589

    
590

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

    
594
  This is usually the case for Physical Volumes under LVM.
595

    
596
  @rtype: boolean
597
  @return: true if the device is available according to sysfs
598

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

    
609

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

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

    
618
  @rtype: boolean
619
  @return: the in-use status of the device
620

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

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

    
636
  if use_blockdev:
637
    return not result.failed
638
  else:
639
    return result.failed
640

    
641

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

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

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

    
653

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

    
657
  """
658
  name = "/dev/%s" % name
659
  return name not in GetSwapInfo()
660

    
661

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

    
665
  """
666
  return not (CheckSysfsHolders(name) and CheckReread(name) and
667
              CheckMounted(name) and CheckSwap(name))
668

    
669

    
670
def WipeDisk(name):
671
  """Wipes a block device.
672

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

    
677
  @param name: the device name (e.g. sda)
678

    
679
  """
680

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

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

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

    
713

    
714
def PartitionDisk(name):
715
  """Partitions a disk.
716

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

    
720
  @param name: the device name, e.g. sda
721

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

    
733

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

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

    
740
  @param name: the device name, e.g. sda
741

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

    
751

    
752
def CreateVG(vgname, disks):
753
  """Creates the volume group.
754

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

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

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

    
769

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

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

    
778
  @param options: the options returned from OptParser.parse_options
779

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

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

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

    
810
  return disklist
811

    
812

    
813
def BootStrap():
814
  """Actual main routine.
815

    
816
  """
817
  CheckPrereq()
818

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

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

    
834

    
835
  disklist = ValidateDiskList(options)
836

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

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

    
853

    
854
def main():
855
  """Application entry point.
856

    
857
  This is just a wrapper over BootStrap, to handle our own exceptions.
858

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

    
902

    
903
if __name__ == "__main__":
904
  main()