Revision a01b500b

b/lib/utils.py
844 844
        raise errors.TypeEnforcementError(msg)
845 845

  
846 846

  
847
def _GetProcStatusPath(pid):
848
  """Returns the path for a PID's proc status file.
849

  
850
  @type pid: int
851
  @param pid: Process ID
852
  @rtype: string
853

  
854
  """
855
  return "/proc/%d/status" % pid
856

  
857

  
847 858
def IsProcessAlive(pid):
848 859
  """Check if a given pid exists on the system.
849 860

  
......
870 881
  if pid <= 0:
871 882
    return False
872 883

  
873
  proc_entry = "/proc/%d/status" % pid
874 884
  # /proc in a multiprocessor environment can have strange behaviors.
875 885
  # Retry the os.stat a few times until we get a good result.
876 886
  try:
877
    return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5, args=[proc_entry])
887
    return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
888
                 args=[_GetProcStatusPath(pid)])
878 889
  except RetryTimeout, err:
879 890
    err.RaiseInner()
880 891

  
881 892

  
893
def _ParseSigsetT(sigset):
894
  """Parse a rendered sigset_t value.
895

  
896
  This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
897
  function.
898

  
899
  @type sigset: string
900
  @param sigset: Rendered signal set from /proc/$pid/status
901
  @rtype: set
902
  @return: Set of all enabled signal numbers
903

  
904
  """
905
  result = set()
906

  
907
  signum = 0
908
  for ch in reversed(sigset):
909
    chv = int(ch, 16)
910

  
911
    # The following could be done in a loop, but it's easier to read and
912
    # understand in the unrolled form
913
    if chv & 1:
914
      result.add(signum + 1)
915
    if chv & 2:
916
      result.add(signum + 2)
917
    if chv & 4:
918
      result.add(signum + 3)
919
    if chv & 8:
920
      result.add(signum + 4)
921

  
922
    signum += 4
923

  
924
  return result
925

  
926

  
927
def _GetProcStatusField(pstatus, field):
928
  """Retrieves a field from the contents of a proc status file.
929

  
930
  @type pstatus: string
931
  @param pstatus: Contents of /proc/$pid/status
932
  @type field: string
933
  @param field: Name of field whose value should be returned
934
  @rtype: string
935

  
936
  """
937
  for line in pstatus.splitlines():
938
    parts = line.split(":", 1)
939

  
940
    if len(parts) < 2 or parts[0] != field:
941
      continue
942

  
943
    return parts[1].strip()
944

  
945
  return None
946

  
947

  
948
def IsProcessHandlingSignal(pid, signum, status_path=None):
949
  """Checks whether a process is handling a signal.
950

  
951
  @type pid: int
952
  @param pid: Process ID
953
  @type signum: int
954
  @param signum: Signal number
955
  @rtype: bool
956

  
957
  """
958
  if status_path is None:
959
    status_path = _GetProcStatusPath(pid)
960

  
961
  try:
962
    proc_status = ReadFile(status_path)
963
  except EnvironmentError, err:
964
    # In at least one case, reading /proc/$pid/status failed with ESRCH.
965
    if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
966
      return False
967
    raise
968

  
969
  sigcgt = _GetProcStatusField(proc_status, "SigCgt")
970
  if sigcgt is None:
971
    raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
972

  
973
  # Now check whether signal is handled
974
  return signum in _ParseSigsetT(sigcgt)
975

  
976

  
882 977
def ReadPidFile(pidfile):
883 978
  """Read a pid from a file.
884 979

  
b/test/ganeti.utils_unittest.py
79 79
                 "nonexisting process detected")
80 80

  
81 81

  
82
class TestGetProcStatusPath(unittest.TestCase):
83
  def test(self):
84
    self.assert_("/1234/" in utils._GetProcStatusPath(1234))
85
    self.assertNotEqual(utils._GetProcStatusPath(1),
86
                        utils._GetProcStatusPath(2))
87

  
88

  
89
class TestIsProcessHandlingSignal(unittest.TestCase):
90
  def setUp(self):
91
    self.tmpdir = tempfile.mkdtemp()
92

  
93
  def tearDown(self):
94
    shutil.rmtree(self.tmpdir)
95

  
96
  def testParseSigsetT(self):
97
    self.assertEqual(len(utils._ParseSigsetT("0")), 0)
98
    self.assertEqual(utils._ParseSigsetT("1"), set([1]))
99
    self.assertEqual(utils._ParseSigsetT("1000a"), set([2, 4, 17]))
100
    self.assertEqual(utils._ParseSigsetT("810002"), set([2, 17, 24, ]))
101
    self.assertEqual(utils._ParseSigsetT("0000000180000202"),
102
                     set([2, 10, 32, 33]))
103
    self.assertEqual(utils._ParseSigsetT("0000000180000002"),
104
                     set([2, 32, 33]))
105
    self.assertEqual(utils._ParseSigsetT("0000000188000002"),
106
                     set([2, 28, 32, 33]))
107
    self.assertEqual(utils._ParseSigsetT("000000004b813efb"),
108
                     set([1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 17,
109
                          24, 25, 26, 28, 31]))
110
    self.assertEqual(utils._ParseSigsetT("ffffff"), set(range(1, 25)))
111

  
112
  def testGetProcStatusField(self):
113
    for field in ["SigCgt", "Name", "FDSize"]:
114
      for value in ["", "0", "cat", "  1234 KB"]:
115
        pstatus = "\n".join([
116
          "VmPeak: 999 kB",
117
          "%s: %s" % (field, value),
118
          "TracerPid: 0",
119
          ])
120
        result = utils._GetProcStatusField(pstatus, field)
121
        self.assertEqual(result, value.strip())
122

  
123
  def test(self):
124
    sp = utils.PathJoin(self.tmpdir, "status")
125

  
126
    utils.WriteFile(sp, data="\n".join([
127
      "Name:   bash",
128
      "State:  S (sleeping)",
129
      "SleepAVG:       98%",
130
      "Pid:    22250",
131
      "PPid:   10858",
132
      "TracerPid:      0",
133
      "SigBlk: 0000000000010000",
134
      "SigIgn: 0000000000384004",
135
      "SigCgt: 000000004b813efb",
136
      "CapEff: 0000000000000000",
137
      ]))
138

  
139
    self.assert_(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
140

  
141
  def testNoSigCgt(self):
142
    sp = utils.PathJoin(self.tmpdir, "status")
143

  
144
    utils.WriteFile(sp, data="\n".join([
145
      "Name:   bash",
146
      ]))
147

  
148
    self.assertRaises(RuntimeError, utils.IsProcessHandlingSignal,
149
                      1234, 10, status_path=sp)
150

  
151
  def testNoSuchFile(self):
152
    sp = utils.PathJoin(self.tmpdir, "notexist")
153

  
154
    self.assertFalse(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
155

  
156
  @staticmethod
157
  def _TestRealProcess():
158
    signal.signal(signal.SIGUSR1, signal.SIG_DFL)
159
    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
160
      raise Exception("SIGUSR1 is handled when it should not be")
161

  
162
    signal.signal(signal.SIGUSR1, lambda signum, frame: None)
163
    if not utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
164
      raise Exception("SIGUSR1 is not handled when it should be")
165

  
166
    signal.signal(signal.SIGUSR1, signal.SIG_IGN)
167
    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
168
      raise Exception("SIGUSR1 is not handled when it should be")
169

  
170
    signal.signal(signal.SIGUSR1, signal.SIG_DFL)
171
    if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
172
      raise Exception("SIGUSR1 is handled when it should not be")
173

  
174
    return True
175

  
176
  def testRealProcess(self):
177
    self.assert_(utils.RunInSeparateProcess(self._TestRealProcess))
178

  
179

  
82 180
class TestPidFileFunctions(unittest.TestCase):
83 181
  """Tests for WritePidFile, RemovePidFile and ReadPidFile"""
84 182

  

Also available in: Unified diff