Revision abdf0113 lib/bdev.py

b/lib/bdev.py
43 43
    - online (=used, or ready for use)
44 44

  
45 45
  A device can also be online but read-only, however we are not using
46
  the readonly state (MD and LV have it, if needed in the future)
47
  and we are usually looking at this like at a stack, so it's easier
48
  to conceptualise the transition from not-existing to online and back
46
  the readonly state (LV has it, if needed in the future) and we are
47
  usually looking at this like at a stack, so it's easier to
48
  conceptualise the transition from not-existing to online and back
49 49
  like a linear one.
50 50

  
51 51
  The many different states of the device are due to the fact that we
52 52
  need to cover many device types:
53 53
    - logical volumes are created, lvchange -a y $lv, and used
54
    - md arrays are created or assembled and used
55 54
    - drbd devices are attached to a local disk/remote peer and made primary
56 55

  
57 56
  A block device is identified by three items:
......
61 60

  
62 61
  Not all devices implement both the first two as distinct items. LVM
63 62
  logical volumes have their unique ID (the pair volume group, logical
64
  volume name) in a 1-to-1 relation to the dev path. For MD devices,
65
  the /dev path is dynamic and the unique ID is the UUID generated at
66
  array creation plus the slave list. For DRBD devices, the /dev path
67
  is again dynamic and the unique id is the pair (host1, dev1),
68
  (host2, dev2).
63
  volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
64
  the /dev path is again dynamic and the unique id is the pair (host1,
65
  dev1), (host2, dev2).
69 66

  
70 67
  You can get to a device in two ways:
71 68
    - creating the (real) device, which returns you
72
      an attached instance (lvcreate, mdadm --create)
69
      an attached instance (lvcreate)
73 70
    - attaching of a python instance to an existing (real) device
74 71

  
75 72
  The second point, the attachement to a device, is different
......
149 146
  def Remove(self):
150 147
    """Remove this device.
151 148

  
152
    This makes sense only for some of the device types: LV and to a
153
    lesser degree, md devices. Also note that if the device can't
154
    attach, the removal can't be completed.
149
    This makes sense only for some of the device types: LV and file
150
    storeage. Also note that if the device can't attach, the removal
151
    can't be completed.
155 152

  
156 153
    """
157 154
    raise NotImplementedError
......
523 520
                                     result.output))
524 521

  
525 522

  
526
class MDRaid1(BlockDev):
527
  """raid1 device implemented via md.
528

  
529
  """
530
  def __init__(self, unique_id, children):
531
    super(MDRaid1, self).__init__(unique_id, children)
532
    self.major = 9
533
    self.Attach()
534

  
535
  def Attach(self):
536
    """Find an array which matches our config and attach to it.
537

  
538
    This tries to find a MD array which has the same UUID as our own.
539

  
540
    """
541
    minor = self._FindMDByUUID(self.unique_id)
542
    if minor is not None:
543
      self._SetFromMinor(minor)
544
    else:
545
      self.minor = None
546
      self.dev_path = None
547

  
548
    return (minor is not None)
549

  
550
  @staticmethod
551
  def _GetUsedDevs():
552
    """Compute the list of in-use MD devices.
553

  
554
    It doesn't matter if the used device have other raid level, just
555
    that they are in use.
556

  
557
    """
558
    mdstat = open("/proc/mdstat", "r")
559
    data = mdstat.readlines()
560
    mdstat.close()
561

  
562
    used_md = {}
563
    valid_line = re.compile("^md([0-9]+) : .*$")
564
    for line in data:
565
      match = valid_line.match(line)
566
      if match:
567
        md_no = int(match.group(1))
568
        used_md[md_no] = line
569

  
570
    return used_md
571

  
572
  @staticmethod
573
  def _GetDevInfo(minor):
574
    """Get info about a MD device.
575

  
576
    Currently only uuid is returned.
577

  
578
    """
579
    result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
580
    if result.failed:
581
      logger.Error("Can't display md: %s - %s" %
582
                   (result.fail_reason, result.output))
583
      return None
584
    retval = {}
585
    for line in result.stdout.splitlines():
586
      line = line.strip()
587
      kv = line.split(" : ", 1)
588
      if kv:
589
        if kv[0] == "UUID":
590
          retval["uuid"] = kv[1].split()[0]
591
        elif kv[0] == "State":
592
          retval["state"] = kv[1].split(", ")
593
    return retval
594

  
595
  @staticmethod
596
  def _FindUnusedMinor():
597
    """Compute an unused MD minor.
598

  
599
    This code assumes that there are 256 minors only.
600

  
601
    """
602
    used_md = MDRaid1._GetUsedDevs()
603
    i = 0
604
    while i < 256:
605
      if i not in used_md:
606
        break
607
      i += 1
608
    if i == 256:
609
      logger.Error("Critical: Out of md minor numbers.")
610
      raise errors.BlockDeviceError("Can't find a free MD minor")
611
    return i
612

  
613
  @classmethod
614
  def _FindMDByUUID(cls, uuid):
615
    """Find the minor of an MD array with a given UUID.
616

  
617
    """
618
    md_list = cls._GetUsedDevs()
619
    for minor in md_list:
620
      info = cls._GetDevInfo(minor)
621
      if info and info["uuid"] == uuid:
622
        return minor
623
    return None
624

  
625
  @staticmethod
626
  def _ZeroSuperblock(dev_path):
627
    """Zero the possible locations for an MD superblock.
628

  
629
    The zero-ing can't be done via ``mdadm --zero-superblock`` as that
630
    fails in versions 2.x with the same error code as non-writable
631
    device.
632

  
633
    The superblocks are located at (negative values are relative to
634
    the end of the block device):
635
      - -128k to end for version 0.90 superblock
636
      - -8k to -12k for version 1.0 superblock (included in the above)
637
      - 0k to 4k for version 1.1 superblock
638
      - 4k to 8k for version 1.2 superblock
639

  
640
    To cover all situations, the zero-ing will be:
641
      - 0k to 128k
642
      - -128k to end
643

  
644
    As such, the minimum device size must be 128k, otherwise we'll get
645
    I/O errors.
646

  
647
    Note that this function depends on the fact that one can open,
648
    read and write block devices normally.
649

  
650
    """
651
    overwrite_size = 128 * 1024
652
    empty_buf = '\0' * overwrite_size
653
    fd = open(dev_path, "r+")
654
    try:
655
      fd.seek(0, 0)
656
      p1 = fd.tell()
657
      fd.write(empty_buf)
658
      p2 = fd.tell()
659
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
660
      fd.seek(-overwrite_size, 2)
661
      p1 = fd.tell()
662
      fd.write(empty_buf)
663
      p2 = fd.tell()
664
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
665
    finally:
666
      fd.close()
667

  
668
  @classmethod
669
  def Create(cls, unique_id, children, size):
670
    """Create a new MD raid1 array.
671

  
672
    """
673
    if not isinstance(children, (tuple, list)):
674
      raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
675
                       str(children))
676
    for i in children:
677
      if not isinstance(i, BlockDev):
678
        raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
679
    for i in children:
680
      try:
681
        cls._ZeroSuperblock(i.dev_path)
682
      except EnvironmentError, err:
683
        logger.Error("Can't zero superblock for %s: %s" %
684
                     (i.dev_path, str(err)))
685
        return None
686
    minor = cls._FindUnusedMinor()
687
    result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
688
                           "--auto=yes", "--force", "-l1",
689
                           "-n%d" % len(children)] +
690
                          [dev.dev_path for dev in children])
691

  
692
    if result.failed:
693
      logger.Error("Can't create md: %s: %s" % (result.fail_reason,
694
                                                result.output))
695
      return None
696
    info = cls._GetDevInfo(minor)
697
    if not info or not "uuid" in info:
698
      logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
699
      return None
700
    return MDRaid1(info["uuid"], children)
701

  
702
  def Remove(self):
703
    """Stub remove function for MD RAID 1 arrays.
704

  
705
    We don't remove the superblock right now. Mark a to do.
706

  
707
    """
708
    #TODO: maybe zero superblock on child devices?
709
    return self.Shutdown()
710

  
711
  def Rename(self, new_id):
712
    """Rename a device.
713

  
714
    This is not supported for md raid1 devices.
715

  
716
    """
717
    raise errors.ProgrammerError("Can't rename a md raid1 device")
718

  
719
  def AddChildren(self, devices):
720
    """Add new member(s) to the md raid1.
721

  
722
    """
723
    if self.minor is None and not self.Attach():
724
      raise errors.BlockDeviceError("Can't attach to device")
725

  
726
    args = ["mdadm", "-a", self.dev_path]
727
    for dev in devices:
728
      if dev.dev_path is None:
729
        raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
730
      dev.Open()
731
      args.append(dev.dev_path)
732
    result = utils.RunCmd(args)
733
    if result.failed:
734
      raise errors.BlockDeviceError("Failed to add new device to array: %s" %
735
                                    result.output)
736
    new_len = len(self._children) + len(devices)
737
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
738
    if result.failed:
739
      raise errors.BlockDeviceError("Can't grow md array: %s" %
740
                                    result.output)
741
    self._children.extend(devices)
742

  
743
  def RemoveChildren(self, devices):
744
    """Remove member(s) from the md raid1.
745

  
746
    """
747
    if self.minor is None and not self.Attach():
748
      raise errors.BlockDeviceError("Can't attach to device")
749
    new_len = len(self._children) - len(devices)
750
    if new_len < 1:
751
      raise errors.BlockDeviceError("Can't reduce to less than one child")
752
    args = ["mdadm", "-f", self.dev_path]
753
    orig_devs = []
754
    for dev in devices:
755
      args.append(dev)
756
      for c in self._children:
757
        if c.dev_path == dev:
758
          orig_devs.append(c)
759
          break
760
      else:
761
        raise errors.BlockDeviceError("Can't find device '%s' for removal" %
762
                                      dev)
763
    result = utils.RunCmd(args)
764
    if result.failed:
765
      raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
766
                                    result.output)
767

  
768
    # it seems here we need a short delay for MD to update its
769
    # superblocks
770
    time.sleep(0.5)
771
    args[1] = "-r"
772
    result = utils.RunCmd(args)
773
    if result.failed:
774
      raise errors.BlockDeviceError("Failed to remove device(s) from array:"
775
                                    " %s" % result.output)
776
    result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
777
                           "-n", new_len])
778
    if result.failed:
779
      raise errors.BlockDeviceError("Can't shrink md array: %s" %
780
                                    result.output)
781
    for dev in orig_devs:
782
      self._children.remove(dev)
783

  
784
  def _SetFromMinor(self, minor):
785
    """Set our parameters based on the given minor.
786

  
787
    This sets our minor variable and our dev_path.
788

  
789
    """
790
    self.minor = minor
791
    self.dev_path = "/dev/md%d" % minor
792

  
793
  def Assemble(self):
794
    """Assemble the MD device.
795

  
796
    At this point we should have:
797
      - list of children devices
798
      - uuid
799

  
800
    """
801
    result = super(MDRaid1, self).Assemble()
802
    if not result:
803
      return result
804
    md_list = self._GetUsedDevs()
805
    for minor in md_list:
806
      info = self._GetDevInfo(minor)
807
      if info and info["uuid"] == self.unique_id:
808
        self._SetFromMinor(minor)
809
        logger.Info("MD array %s already started" % str(self))
810
        return True
811
    free_minor = self._FindUnusedMinor()
812
    result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
813
                           self.unique_id, "/dev/md%d" % free_minor] +
814
                          [bdev.dev_path for bdev in self._children])
815
    if result.failed:
816
      logger.Error("Can't assemble MD array: %s: %s" %
817
                   (result.fail_reason, result.output))
818
      self.minor = None
819
    else:
820
      self.minor = free_minor
821
    return not result.failed
822

  
823
  def Shutdown(self):
824
    """Tear down the MD array.
825

  
826
    This does a 'mdadm --stop' so after this command, the array is no
827
    longer available.
828

  
829
    """
830
    if self.minor is None and not self.Attach():
831
      logger.Info("MD object not attached to a device")
832
      return True
833

  
834
    result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
835
    if result.failed:
836
      logger.Error("Can't stop MD array: %s - %s" %
837
                   (result.fail_reason, result.output))
838
      return False
839
    self.minor = None
840
    self.dev_path = None
841
    return True
842

  
843
  def SetSyncSpeed(self, kbytes):
844
    """Set the maximum sync speed for the MD array.
845

  
846
    """
847
    result = super(MDRaid1, self).SetSyncSpeed(kbytes)
848
    if self.minor is None:
849
      logger.Error("MD array not attached to a device")
850
      return False
851
    f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
852
    try:
853
      f.write("%d" % kbytes)
854
    finally:
855
      f.close()
856
    f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
857
    try:
858
      f.write("%d" % (kbytes/2))
859
    finally:
860
      f.close()
861
    return result
862

  
863
  def GetSyncStatus(self):
864
    """Returns the sync status of the device.
865

  
866
    Returns:
867
     (sync_percent, estimated_time, is_degraded, ldisk)
868

  
869
    If sync_percent is None, it means all is ok
870
    If estimated_time is None, it means we can't esimate
871
    the time needed, otherwise it's the time left in seconds.
872

  
873
    The ldisk parameter is always true for MD devices.
874

  
875
    """
876
    if self.minor is None and not self.Attach():
877
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
878
    dev_info = self._GetDevInfo(self.minor)
879
    is_clean = ("state" in dev_info and
880
                len(dev_info["state"]) == 1 and
881
                dev_info["state"][0] in ("clean", "active"))
882
    sys_path = "/sys/block/md%s/md/" % self.minor
883
    f = file(sys_path + "sync_action")
884
    sync_status = f.readline().strip()
885
    f.close()
886
    if sync_status == "idle":
887
      return None, None, not is_clean, False
888
    f = file(sys_path + "sync_completed")
889
    sync_completed = f.readline().strip().split(" / ")
890
    f.close()
891
    if len(sync_completed) != 2:
892
      return 0, None, not is_clean, False
893
    sync_done, sync_total = [float(i) for i in sync_completed]
894
    sync_percent = 100.0*sync_done/sync_total
895
    f = file(sys_path + "sync_speed")
896
    sync_speed_k = int(f.readline().strip())
897
    if sync_speed_k == 0:
898
      time_est = None
899
    else:
900
      time_est = (sync_total - sync_done) / 2 / sync_speed_k
901
    return sync_percent, time_est, not is_clean, False
902

  
903
  def Open(self, force=False):
904
    """Make the device ready for I/O.
905

  
906
    This is a no-op for the MDRaid1 device type, although we could use
907
    the 2.6.18's new array_state thing.
908

  
909
    """
910
    pass
911

  
912
  def Close(self):
913
    """Notifies that the device will no longer be used for I/O.
914

  
915
    This is a no-op for the MDRaid1 device type, but see comment for
916
    `Open()`.
917

  
918
    """
919
    pass
920

  
921

  
922 523
class BaseDRBD(BlockDev):
923 524
  """Base DRBD class.
924 525

  
925 526
  This class contains a few bits of common functionality between the
926 527
  0.7 and 8.x versions of DRBD.
927 528

  
928
  """
929
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
930
                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
931

  
932
  _DRBD_MAJOR = 147
933
  _ST_UNCONFIGURED = "Unconfigured"
934
  _ST_WFCONNECTION = "WFConnection"
935
  _ST_CONNECTED = "Connected"
936

  
937
  @staticmethod
938
  def _GetProcData():
939
    """Return data from /proc/drbd.
940

  
941
    """
942
    stat = open("/proc/drbd", "r")
943
    try:
944
      data = stat.read().splitlines()
945
    finally:
946
      stat.close()
947
    if not data:
948
      raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
949
    return data
950

  
951
  @staticmethod
952
  def _MassageProcData(data):
953
    """Transform the output of _GetProdData into a nicer form.
954

  
955
    Returns:
956
      a dictionary of minor: joined lines from /proc/drbd for that minor
957

  
958
    """
959
    lmatch = re.compile("^ *([0-9]+):.*$")
960
    results = {}
961
    old_minor = old_line = None
962
    for line in data:
963
      lresult = lmatch.match(line)
964
      if lresult is not None:
965
        if old_minor is not None:
966
          results[old_minor] = old_line
967
        old_minor = int(lresult.group(1))
968
        old_line = line
969
      else:
970
        if old_minor is not None:
971
          old_line += " " + line.strip()
972
    # add last line
973
    if old_minor is not None:
974
      results[old_minor] = old_line
975
    return results
976

  
977
  @classmethod
978
  def _GetVersion(cls):
979
    """Return the DRBD version.
980

  
981
    This will return a dict with keys:
982
      k_major,
983
      k_minor,
984
      k_point,
985
      api,
986
      proto,
987
      proto2 (only on drbd > 8.2.X)
988

  
989
    """
990
    proc_data = cls._GetProcData()
991
    first_line = proc_data[0].strip()
992
    version = cls._VERSION_RE.match(first_line)
993
    if not version:
994
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
995
                                    first_line)
996

  
997
    values = version.groups()
998
    retval = {'k_major': int(values[0]),
999
              'k_minor': int(values[1]),
1000
              'k_point': int(values[2]),
1001
              'api': int(values[3]),
1002
              'proto': int(values[4]),
1003
             }
1004
    if values[5] is not None:
1005
      retval['proto2'] = values[5]
1006

  
1007
    return retval
1008

  
1009
  @staticmethod
1010
  def _DevPath(minor):
1011
    """Return the path to a drbd device for a given minor.
1012

  
1013
    """
1014
    return "/dev/drbd%d" % minor
1015

  
1016
  @classmethod
1017
  def _GetUsedDevs(cls):
1018
    """Compute the list of used DRBD devices.
1019

  
1020
    """
1021
    data = cls._GetProcData()
1022

  
1023
    used_devs = {}
1024
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1025
    for line in data:
1026
      match = valid_line.match(line)
1027
      if not match:
1028
        continue
1029
      minor = int(match.group(1))
1030
      state = match.group(2)
1031
      if state == cls._ST_UNCONFIGURED:
1032
        continue
1033
      used_devs[minor] = state, line
1034

  
1035
    return used_devs
1036

  
1037
  def _SetFromMinor(self, minor):
1038
    """Set our parameters based on the given minor.
1039

  
1040
    This sets our minor variable and our dev_path.
1041

  
1042
    """
1043
    if minor is None:
1044
      self.minor = self.dev_path = None
1045
    else:
1046
      self.minor = minor
1047
      self.dev_path = self._DevPath(minor)
1048

  
1049
  @staticmethod
1050
  def _CheckMetaSize(meta_device):
1051
    """Check if the given meta device looks like a valid one.
1052

  
1053
    This currently only check the size, which must be around
1054
    128MiB.
1055

  
1056
    """
1057
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1058
    if result.failed:
1059
      logger.Error("Failed to get device size: %s - %s" %
1060
                   (result.fail_reason, result.output))
1061
      return False
1062
    try:
1063
      sectors = int(result.stdout)
1064
    except ValueError:
1065
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1066
      return False
1067
    bytes = sectors * 512
1068
    if bytes < 128 * 1024 * 1024: # less than 128MiB
1069
      logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1070
      return False
1071
    if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1072
      logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1073
      return False
1074
    return True
1075

  
1076
  def Rename(self, new_id):
1077
    """Rename a device.
1078

  
1079
    This is not supported for drbd devices.
1080

  
1081
    """
1082
    raise errors.ProgrammerError("Can't rename a drbd device")
1083

  
1084

  
1085
class DRBDev(BaseDRBD):
1086
  """DRBD block device.
1087

  
1088
  This implements the local host part of the DRBD device, i.e. it
1089
  doesn't do anything to the supposed peer. If you need a fully
1090
  connected DRBD pair, you need to use this class on both hosts.
1091

  
1092
  The unique_id for the drbd device is the (local_ip, local_port,
1093
  remote_ip, remote_port) tuple, and it must have two children: the
1094
  data device and the meta_device. The meta device is checked for
1095
  valid size and is zeroed on create.
1096

  
1097
  """
1098
  def __init__(self, unique_id, children):
1099
    super(DRBDev, self).__init__(unique_id, children)
1100
    self.major = self._DRBD_MAJOR
1101
    version = self._GetVersion()
1102
    if version['k_major'] != 0 and version['k_minor'] != 7:
1103
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1104
                                    " requested ganeti usage: kernel is"
1105
                                    " %s.%s, ganeti wants 0.7" %
1106
                                    (version['k_major'], version['k_minor']))
1107
    if len(children) != 2:
1108
      raise ValueError("Invalid configuration data %s" % str(children))
1109
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1110
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1111
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1112
    self.Attach()
1113

  
1114
  @classmethod
1115
  def _FindUnusedMinor(cls):
1116
    """Find an unused DRBD device.
1117

  
1118
    """
1119
    data = cls._GetProcData()
1120

  
1121
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1122
    for line in data:
1123
      match = valid_line.match(line)
1124
      if match:
1125
        return int(match.group(1))
1126
    logger.Error("Error: no free drbd minors!")
1127
    raise errors.BlockDeviceError("Can't find a free DRBD minor")
1128

  
1129
  @classmethod
1130
  def _GetDevInfo(cls, minor):
1131
    """Get details about a given DRBD minor.
1132

  
1133
    This return, if available, the local backing device in (major,
1134
    minor) formant and the local and remote (ip, port) information.
1135

  
1136
    """
1137
    data = {}
1138
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1139
    if result.failed:
1140
      logger.Error("Can't display the drbd config: %s - %s" %
1141
                   (result.fail_reason, result.output))
1142
      return data
1143
    out = result.stdout
1144
    if out == "Not configured\n":
1145
      return data
1146
    for line in out.splitlines():
1147
      if "local_dev" not in data:
1148
        match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1149
        if match:
1150
          data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1151
          continue
1152
      if "meta_dev" not in data:
1153
        match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1154
        if match:
1155
          if match.group(2) is not None and match.group(3) is not None:
1156
            # matched on the major/minor
1157
            data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1158
          else:
1159
            # matched on the "internal" string
1160
            data["meta_dev"] = match.group(1)
1161
            # in this case, no meta_index is in the output
1162
            data["meta_index"] = -1
1163
          continue
1164
      if "meta_index" not in data:
1165
        match = re.match("^Meta index: ([0-9]+).*$", line)
1166
        if match:
1167
          data["meta_index"] = int(match.group(1))
1168
          continue
1169
      if "local_addr" not in data:
1170
        match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1171
        if match:
1172
          data["local_addr"] = (match.group(1), int(match.group(2)))
1173
          continue
1174
      if "remote_addr" not in data:
1175
        match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1176
        if match:
1177
          data["remote_addr"] = (match.group(1), int(match.group(2)))
1178
          continue
1179
    return data
1180

  
1181
  def _MatchesLocal(self, info):
1182
    """Test if our local config matches with an existing device.
1183

  
1184
    The parameter should be as returned from `_GetDevInfo()`. This
1185
    method tests if our local backing device is the same as the one in
1186
    the info parameter, in effect testing if we look like the given
1187
    device.
1188

  
1189
    """
1190
    if not ("local_dev" in info and "meta_dev" in info and
1191
            "meta_index" in info):
1192
      return False
1193

  
1194
    backend = self._children[0]
1195
    if backend is not None:
1196
      retval = (info["local_dev"] == (backend.major, backend.minor))
1197
    else:
1198
      retval = (info["local_dev"] == (0, 0))
1199
    meta = self._children[1]
1200
    if meta is not None:
1201
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1202
      retval = retval and (info["meta_index"] == 0)
1203
    else:
1204
      retval = retval and (info["meta_dev"] == "internal" and
1205
                           info["meta_index"] == -1)
1206
    return retval
1207

  
1208
  def _MatchesNet(self, info):
1209
    """Test if our network config matches with an existing device.
1210

  
1211
    The parameter should be as returned from `_GetDevInfo()`. This
1212
    method tests if our network configuration is the same as the one
1213
    in the info parameter, in effect testing if we look like the given
1214
    device.
1215

  
1216
    """
1217
    if (((self._lhost is None and not ("local_addr" in info)) and
1218
         (self._rhost is None and not ("remote_addr" in info)))):
1219
      return True
1220

  
1221
    if self._lhost is None:
1222
      return False
1223

  
1224
    if not ("local_addr" in info and
1225
            "remote_addr" in info):
1226
      return False
1227

  
1228
    retval = (info["local_addr"] == (self._lhost, self._lport))
1229
    retval = (retval and
1230
              info["remote_addr"] == (self._rhost, self._rport))
1231
    return retval
1232

  
1233
  @classmethod
1234
  def _AssembleLocal(cls, minor, backend, meta):
1235
    """Configure the local part of a DRBD device.
1236

  
1237
    This is the first thing that must be done on an unconfigured DRBD
1238
    device. And it must be done only once.
1239

  
1240
    """
1241
    if not cls._CheckMetaSize(meta):
1242
      return False
1243
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1244
                           backend, meta, "0", "-e", "detach"])
1245
    if result.failed:
1246
      logger.Error("Can't attach local disk: %s" % result.output)
1247
    return not result.failed
1248

  
1249
  @classmethod
1250
  def _ShutdownLocal(cls, minor):
1251
    """Detach from the local device.
1252

  
1253
    I/Os will continue to be served from the remote device. If we
1254
    don't have a remote device, this operation will fail.
1255

  
1256
    """
1257
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1258
    if result.failed:
1259
      logger.Error("Can't detach local device: %s" % result.output)
1260
    return not result.failed
1261

  
1262
  @staticmethod
1263
  def _ShutdownAll(minor):
1264
    """Deactivate the device.
1265

  
1266
    This will, of course, fail if the device is in use.
1267

  
1268
    """
1269
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1270
    if result.failed:
1271
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1272
    return not result.failed
1273

  
1274
  @classmethod
1275
  def _AssembleNet(cls, minor, net_info, protocol):
1276
    """Configure the network part of the device.
1277

  
1278
    This operation can be, in theory, done multiple times, but there
1279
    have been cases (in lab testing) in which the network part of the
1280
    device had become stuck and couldn't be shut down because activity
1281
    from the new peer (also stuck) triggered a timer re-init and
1282
    needed remote peer interface shutdown in order to clear. So please
1283
    don't change online the net config.
1284

  
1285
    """
1286
    lhost, lport, rhost, rport = net_info
1287
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1288
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1289
                           protocol])
1290
    if result.failed:
1291
      logger.Error("Can't setup network for dbrd device: %s - %s" %
1292
                   (result.fail_reason, result.output))
1293
      return False
1294

  
1295
    timeout = time.time() + 10
1296
    ok = False
1297
    while time.time() < timeout:
1298
      info = cls._GetDevInfo(minor)
1299
      if not "local_addr" in info or not "remote_addr" in info:
1300
        time.sleep(1)
1301
        continue
1302
      if (info["local_addr"] != (lhost, lport) or
1303
          info["remote_addr"] != (rhost, rport)):
1304
        time.sleep(1)
1305
        continue
1306
      ok = True
1307
      break
1308
    if not ok:
1309
      logger.Error("Timeout while configuring network")
1310
      return False
1311
    return True
1312

  
1313
  @classmethod
1314
  def _ShutdownNet(cls, minor):
1315
    """Disconnect from the remote peer.
1316

  
1317
    This fails if we don't have a local device.
1318

  
1319
    """
1320
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1321
    if result.failed:
1322
      logger.Error("Can't shutdown network: %s" % result.output)
1323
    return not result.failed
1324

  
1325
  def Assemble(self):
1326
    """Assemble the drbd.
1327

  
1328
    Method:
1329
      - if we have a local backing device, we bind to it by:
1330
        - checking the list of used drbd devices
1331
        - check if the local minor use of any of them is our own device
1332
        - if yes, abort?
1333
        - if not, bind
1334
      - if we have a local/remote net info:
1335
        - redo the local backing device step for the remote device
1336
        - check if any drbd device is using the local port,
1337
          if yes abort
1338
        - check if any remote drbd device is using the remote
1339
          port, if yes abort (for now)
1340
        - bind our net port
1341
        - bind the remote net port
1342

  
1343
    """
1344
    self.Attach()
1345
    if self.minor is not None:
1346
      logger.Info("Already assembled")
1347
      return True
1348

  
1349
    result = super(DRBDev, self).Assemble()
1350
    if not result:
1351
      return result
529
  """
530
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
531
                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
1352 532

  
1353
    minor = self._FindUnusedMinor()
1354
    need_localdev_teardown = False
1355
    if self._children[0]:
1356
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1357
                                   self._children[1].dev_path)
1358
      if not result:
1359
        return False
1360
      need_localdev_teardown = True
1361
    if self._lhost and self._lport and self._rhost and self._rport:
1362
      result = self._AssembleNet(minor,
1363
                                 (self._lhost, self._lport,
1364
                                  self._rhost, self._rport),
1365
                                 "C")
1366
      if not result:
1367
        if need_localdev_teardown:
1368
          # we will ignore failures from this
1369
          logger.Error("net setup failed, tearing down local device")
1370
          self._ShutdownAll(minor)
1371
        return False
1372
    self._SetFromMinor(minor)
1373
    return True
533
  _DRBD_MAJOR = 147
534
  _ST_UNCONFIGURED = "Unconfigured"
535
  _ST_WFCONNECTION = "WFConnection"
536
  _ST_CONNECTED = "Connected"
1374 537

  
1375
  def Shutdown(self):
1376
    """Shutdown the DRBD device.
538
  @staticmethod
539
  def _GetProcData():
540
    """Return data from /proc/drbd.
1377 541

  
1378 542
    """
1379
    if self.minor is None and not self.Attach():
1380
      logger.Info("DRBD device not attached to a device during Shutdown")
1381
      return True
1382
    if not self._ShutdownAll(self.minor):
1383
      return False
1384
    self.minor = None
1385
    self.dev_path = None
1386
    return True
543
    stat = open("/proc/drbd", "r")
544
    try:
545
      data = stat.read().splitlines()
546
    finally:
547
      stat.close()
548
    if not data:
549
      raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
550
    return data
1387 551

  
1388
  def Attach(self):
1389
    """Find a DRBD device which matches our config and attach to it.
552
  @staticmethod
553
  def _MassageProcData(data):
554
    """Transform the output of _GetProdData into a nicer form.
1390 555

  
1391
    In case of partially attached (local device matches but no network
1392
    setup), we perform the network attach. If successful, we re-test
1393
    the attach if can return success.
556
    Returns:
557
      a dictionary of minor: joined lines from /proc/drbd for that minor
1394 558

  
1395 559
    """
1396
    for minor in self._GetUsedDevs():
1397
      info = self._GetDevInfo(minor)
1398
      match_l = self._MatchesLocal(info)
1399
      match_r = self._MatchesNet(info)
1400
      if match_l and match_r:
1401
        break
1402
      if match_l and not match_r and "local_addr" not in info:
1403
        res_r = self._AssembleNet(minor,
1404
                                  (self._lhost, self._lport,
1405
                                   self._rhost, self._rport),
1406
                                  "C")
1407
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1408
          break
1409
    else:
1410
      minor = None
1411

  
1412
    self._SetFromMinor(minor)
1413
    return minor is not None
560
    lmatch = re.compile("^ *([0-9]+):.*$")
561
    results = {}
562
    old_minor = old_line = None
563
    for line in data:
564
      lresult = lmatch.match(line)
565
      if lresult is not None:
566
        if old_minor is not None:
567
          results[old_minor] = old_line
568
        old_minor = int(lresult.group(1))
569
        old_line = line
570
      else:
571
        if old_minor is not None:
572
          old_line += " " + line.strip()
573
    # add last line
574
    if old_minor is not None:
575
      results[old_minor] = old_line
576
    return results
1414 577

  
1415
  def Open(self, force=False):
1416
    """Make the local state primary.
578
  @classmethod
579
  def _GetVersion(cls):
580
    """Return the DRBD version.
1417 581

  
1418
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1419
    is given. Since this is a pottentialy dangerous operation, the
1420
    force flag should be only given after creation, when it actually
1421
    has to be given.
582
    This will return a dict with keys:
583
      k_major,
584
      k_minor,
585
      k_point,
586
      api,
587
      proto,
588
      proto2 (only on drbd > 8.2.X)
1422 589

  
1423 590
    """
1424
    if self.minor is None and not self.Attach():
1425
      logger.Error("DRBD cannot attach to a device during open")
1426
      return False
1427
    cmd = ["drbdsetup", self.dev_path, "primary"]
1428
    if force:
1429
      cmd.append("--do-what-I-say")
1430
    result = utils.RunCmd(cmd)
1431
    if result.failed:
1432
      msg = ("Can't make drbd device primary: %s" % result.output)
1433
      logger.Error(msg)
1434
      raise errors.BlockDeviceError(msg)
591
    proc_data = cls._GetProcData()
592
    first_line = proc_data[0].strip()
593
    version = cls._VERSION_RE.match(first_line)
594
    if not version:
595
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
596
                                    first_line)
1435 597

  
1436
  def Close(self):
1437
    """Make the local state secondary.
598
    values = version.groups()
599
    retval = {'k_major': int(values[0]),
600
              'k_minor': int(values[1]),
601
              'k_point': int(values[2]),
602
              'api': int(values[3]),
603
              'proto': int(values[4]),
604
             }
605
    if values[5] is not None:
606
      retval['proto2'] = values[5]
1438 607

  
1439
    This will, of course, fail if the device is in use.
608
    return retval
609

  
610
  @staticmethod
611
  def _DevPath(minor):
612
    """Return the path to a drbd device for a given minor.
1440 613

  
1441 614
    """
1442
    if self.minor is None and not self.Attach():
1443
      logger.Info("Instance not attached to a device")
1444
      raise errors.BlockDeviceError("Can't find device")
1445
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1446
    if result.failed:
1447
      msg = ("Can't switch drbd device to"
1448
             " secondary: %s" % result.output)
1449
      logger.Error(msg)
1450
      raise errors.BlockDeviceError(msg)
615
    return "/dev/drbd%d" % minor
1451 616

  
1452
  def SetSyncSpeed(self, kbytes):
1453
    """Set the speed of the DRBD syncer.
617
  @classmethod
618
  def _GetUsedDevs(cls):
619
    """Compute the list of used DRBD devices.
1454 620

  
1455 621
    """
1456
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1457
    if self.minor is None:
1458
      logger.Info("Instance not attached to a device")
1459
      return False
1460
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1461
                           kbytes])
1462
    if result.failed:
1463
      logger.Error("Can't change syncer rate: %s - %s" %
1464
                   (result.fail_reason, result.output))
1465
    return not result.failed and children_result
622
    data = cls._GetProcData()
1466 623

  
1467
  def GetSyncStatus(self):
1468
    """Returns the sync status of the device.
624
    used_devs = {}
625
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
626
    for line in data:
627
      match = valid_line.match(line)
628
      if not match:
629
        continue
630
      minor = int(match.group(1))
631
      state = match.group(2)
632
      if state == cls._ST_UNCONFIGURED:
633
        continue
634
      used_devs[minor] = state, line
1469 635

  
1470
    Returns:
1471
     (sync_percent, estimated_time, is_degraded, ldisk)
636
    return used_devs
1472 637

  
1473
    If sync_percent is None, it means all is ok
1474
    If estimated_time is None, it means we can't esimate
1475
    the time needed, otherwise it's the time left in seconds.
638
  def _SetFromMinor(self, minor):
639
    """Set our parameters based on the given minor.
1476 640

  
1477
    The ldisk parameter will be returned as True, since the DRBD7
1478
    devices have not been converted.
641
    This sets our minor variable and our dev_path.
1479 642

  
1480 643
    """
1481
    if self.minor is None and not self.Attach():
1482
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1483
    proc_info = self._MassageProcData(self._GetProcData())
1484
    if self.minor not in proc_info:
1485
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1486
                                    self.minor)
1487
    line = proc_info[self.minor]
1488
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1489
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1490
    if match:
1491
      sync_percent = float(match.group(1))
1492
      hours = int(match.group(2))
1493
      minutes = int(match.group(3))
1494
      seconds = int(match.group(4))
1495
      est_time = hours * 3600 + minutes * 60 + seconds
644
    if minor is None:
645
      self.minor = self.dev_path = None
1496 646
    else:
1497
      sync_percent = None
1498
      est_time = None
1499
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1500
    if not match:
1501
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1502
                                    self.minor)
1503
    client_state = match.group(1)
1504
    is_degraded = client_state != "Connected"
1505
    return sync_percent, est_time, is_degraded, False
647
      self.minor = minor
648
      self.dev_path = self._DevPath(minor)
1506 649

  
1507 650
  @staticmethod
1508
  def _ZeroDevice(device):
1509
    """Zero a device.
651
  def _CheckMetaSize(meta_device):
652
    """Check if the given meta device looks like a valid one.
1510 653

  
1511
    This writes until we get ENOSPC.
654
    This currently only check the size, which must be around
655
    128MiB.
1512 656

  
1513 657
    """
1514
    f = open(device, "w")
1515
    buf = "\0" * 1048576
658
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
659
    if result.failed:
660
      logger.Error("Failed to get device size: %s - %s" %
661
                   (result.fail_reason, result.output))
662
      return False
1516 663
    try:
1517
      while True:
1518
        f.write(buf)
1519
    except IOError, err:
1520
      if err.errno != errno.ENOSPC:
1521
        raise
1522

  
1523
  @classmethod
1524
  def Create(cls, unique_id, children, size):
1525
    """Create a new DRBD device.
1526

  
1527
    Since DRBD devices are not created per se, just assembled, this
1528
    function just zeroes the meta device.
664
      sectors = int(result.stdout)
665
    except ValueError:
666
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
667
      return False
668
    bytes = sectors * 512
669
    if bytes < 128 * 1024 * 1024: # less than 128MiB
670
      logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
671
      return False
672
    if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
673
      logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
674
      return False
675
    return True
1529 676

  
1530
    """
1531
    if len(children) != 2:
1532
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1533
    meta = children[1]
1534
    meta.Assemble()
1535
    if not meta.Attach():
1536
      raise errors.BlockDeviceError("Can't attach to meta device")
1537
    if not cls._CheckMetaSize(meta.dev_path):
1538
      raise errors.BlockDeviceError("Invalid meta device")
1539
    logger.Info("Started zeroing device %s" % meta.dev_path)
1540
    cls._ZeroDevice(meta.dev_path)
1541
    logger.Info("Done zeroing device %s" % meta.dev_path)
1542
    return cls(unique_id, children)
677
  def Rename(self, new_id):
678
    """Rename a device.
1543 679

  
1544
  def Remove(self):
1545
    """Stub remove for DRBD devices.
680
    This is not supported for drbd devices.
1546 681

  
1547 682
    """
1548
    return self.Shutdown()
683
    raise errors.ProgrammerError("Can't rename a drbd device")
1549 684

  
1550 685

  
1551 686
class DRBD8(BaseDRBD):
......
2198 1333

  
2199 1334
class FileStorage(BlockDev):
2200 1335
  """File device.
2201
  
1336

  
2202 1337
  This class represents the a file storage backend device.
2203 1338

  
2204 1339
  The unique_id for the file device is a (file_driver, file_path) tuple.
2205
  
1340

  
2206 1341
  """
2207 1342
  def __init__(self, unique_id, children):
2208 1343
    """Initalizes a file device backend.
......
2310 1445

  
2311 1446
DEV_MAP = {
2312 1447
  constants.LD_LV: LogicalVolume,
2313
  constants.LD_MD_R1: MDRaid1,
2314
  constants.LD_DRBD7: DRBDev,
2315 1448
  constants.LD_DRBD8: DRBD8,
2316 1449
  constants.LD_FILE: FileStorage,
2317 1450
  }

Also available in: Unified diff