Statistics
| Branch: | Tag: | Revision:

root / tools / lvmstrap @ 705ee6df

History | View | Annotate | Download (26.2 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 GetSwapInfo():
493
  """Reads /proc/swaps and returns the list of swap backing stores.
494

    
495
  """
496
  swaplines = ReadFile("/proc/swaps").splitlines()[1:]
497
  return [line.split(None, 1)[0] for line in swaplines]
498

    
499

    
500
def DevInfo(name, dev, mountinfo):
501
  """Computes miscellaneous information about a block device.
502

    
503
  @type name: string
504
  @param name: the device name, e.g. sda
505

    
506
  @return: a tuple (mpath, whatvg, fileinfo), where:
507
      - mpath is the mount path where this device is mounted or None
508
      - whatvg is the result of the ReadPV function
509
      - fileinfo is the output of file -bs on the device
510

    
511
  """
512
  if dev in mountinfo:
513
    mpath = mountinfo[dev]
514
  else:
515
    mpath = None
516

    
517
  whatvg = ReadPV(name)
518

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

    
525

    
526
def ShowDiskInfo(opts):
527
  """Shows a nicely formatted block device list for this system.
528

    
529
  This function shows the user a table with the information gathered
530
  by the other functions defined, in order to help the user make a
531
  choice about which disks should be allocated to our volume group.
532

    
533
  """
534
  mounts = GetMountInfo()
535
  dlist = GetDiskList(opts)
536

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

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

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

    
571
    if len(name) > 3:
572
      # Indent partitions
573
      name = " %s" % name
574

    
575
    strlist.append([name, "%.2f" % (float(size) / 1024 / 1024),
576
                    in_use, mp, lvminfo, fileinfo])
577

    
578
  data = cli.GenerateTable(headers, fields, None,
579
                           strlist, numfields=["size"])
580

    
581
  for line in data:
582
    print line
583

    
584

    
585
def CheckSysfsHolders(name):
586
  """Check to see if a device is 'hold' at sysfs level.
587

    
588
  This is usually the case for Physical Volumes under LVM.
589

    
590
  @rtype: boolean
591
  @return: true if the device is available according to sysfs
592

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

    
603

    
604
def CheckReread(name):
605
  """Check to see if a block device is in use.
606

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

    
612
  @rtype: boolean
613
  @return: the in-use status of the device
614

    
615
  """
616
  use_blockdev = IsPartitioned(name)
617
  if use_blockdev:
618
    cmd = "blockdev --rereadpt /dev/%s" % name
619
  else:
620
    cmd = "fuser -vam /dev/%s" % name
621

    
622
  for _ in range(3):
623
    result = ExecCommand(cmd)
624
    if not use_blockdev and result.failed:
625
      break
626
    elif not result.failed:
627
      break
628
    time.sleep(2)
629

    
630
  if use_blockdev:
631
    return not result.failed
632
  else:
633
    return result.failed
634

    
635

    
636
def CheckMounted(name):
637
  """Check to see if a block device is a mountpoint.
638

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

    
642
  """
643
  minfo = GetMountInfo()
644
  dev = ReadDev("/sys/block/%s" % name)
645
  return dev not in minfo
646

    
647

    
648
def CheckSwap(name):
649
  """Check to see if a block device is being used as swap.
650

    
651
  """
652
  name = "/dev/%s" % name
653
  return name not in GetSwapInfo()
654

    
655

    
656
def InUse(name):
657
  """Returns if a disk is in use or not.
658

    
659
  """
660
  return not (CheckSysfsHolders(name) and CheckReread(name) and
661
              CheckMounted(name) and CheckSwap(name))
662

    
663

    
664
def WipeDisk(name):
665
  """Wipes a block device.
666

    
667
  This function wipes a block device, by clearing and re-reading the
668
  partition table. If not successful, it writes back the old partition
669
  data, and leaves the cleanup to the user.
670

    
671
  @param name: the device name (e.g. sda)
672

    
673
  """
674

    
675
  if InUse(name):
676
    raise OperationalError("CRITICAL: disk %s you selected seems to be in"
677
                           " use. ABORTING!" % name)
678

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

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

    
707

    
708
def PartitionDisk(name):
709
  """Partitions a disk.
710

    
711
  This function creates a single partition spanning the entire disk,
712
  by means of fdisk.
713

    
714
  @param name: the device name, e.g. sda
715

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

    
727

    
728
def CreatePVOnDisk(name):
729
  """Creates a physical volume on a block device.
730

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

    
734
  @param name: the device name, e.g. sda
735

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

    
745

    
746
def CreateVG(vgname, disks):
747
  """Creates the volume group.
748

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

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

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

    
763

    
764
def ValidateDiskList(options):
765
  """Validates or computes the disk list for create.
766

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

    
772
  @param options: the options returned from OptParser.parse_options
773

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

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

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

    
804
  return disklist
805

    
806

    
807
def BootStrap():
808
  """Actual main routine.
809

    
810
  """
811
  CheckPrereq()
812

    
813
  options, args = ParseOptions()
814
  vgname = options.vgname
815
  command = args.pop(0)
816
  if command == "diskinfo":
817
    ShowDiskInfo(options)
818
    return
819
  if command != "create":
820
    Usage()
821

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

    
828

    
829
  disklist = ValidateDiskList(options)
830

    
831
  for disk in disklist:
832
    WipeDisk(disk)
833
    if IsPartitioned(disk):
834
      PartitionDisk(disk)
835
  for disk in disklist:
836
    CreatePVOnDisk(disk)
837
  CreateVG(vgname, disklist)
838

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

    
847

    
848
def main():
849
  """Application entry point.
850

    
851
  This is just a wrapper over BootStrap, to handle our own exceptions.
852

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

    
896

    
897
if __name__ == "__main__":
898
  main()