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