fixup! Implement Runtime wrapper
[deliverable/lttng-ivc.git] / lttng_ivc / utils / runtime.py
1 import os
2 import sys
3 import shlex
4 import subprocess
5 import uuid
6 import logging
7 import shutil
8 import contextlib
9 import pprint
10 import signal
11
12 from tempfile import TemporaryDirectory
13
14 import lttng_ivc.settings as Settings
15 _logger = logging.getLogger("Runtime")
16
17
18 @contextlib.contextmanager
19 def get_runtime(runtime_dir):
20 runtime = Runtime(runtime_dir)
21 try:
22 yield runtime
23 finally:
24 runtime.close()
25
26
27 class Runtime(object):
28 def __init__(self, runtime_dir):
29 """
30 A dictionary of popen object eg. lttng-sessiond, relayd,
31 anything really. Key is a uuid.
32 """
33 self.__subprocess = {}
34 self.__stdout_stderr = {}
35 self.__projects = []
36
37 self.__runtime_log = os.path.join(runtime_dir, "log")
38 self.__runtime_log_sub = os.path.join(self.__runtime_log, "subprocess")
39
40 """
41 Path of the copy of lttng_home folder after Runtime.close() is issued. This is
42 to be used for post runtime analysis and mostly debugging on error.
43 """
44 self.__post_runtime_lttng_home_path = os.path.join(runtime_dir,
45 "lttng_home")
46
47 self._runtime_log_aggregation = os.path.join(self.__runtime_log, "runtime.log")
48
49 self._run_command_count = 0
50 self._is_test_modules_loaded = False
51
52 self.special_env_variables = {"LTTNG_UST_DEBUG": "1",
53 "LTTNG_APP_SOCKET_TIMEOUT": "-1",
54 #"LTTNG_UST_REGISTER_TIMEOUT": "-1",
55 "LTTNG_NETWORK_SOCKET_TIMEOUT": "-1"}
56
57 # Keep a reference on the object to keep it alive. It will close/clean on
58 # exit.
59 self.__lttng_home_dir = TemporaryDirectory(prefix=Settings.tmp_object_prefix)
60 self.lttng_home = self.__lttng_home_dir.name
61
62 if len(self.lttng_home) > 88:
63 raise Exception("TemporaryDirectory for lttng_home is to long. Use a short TMPDIR")
64
65 os.makedirs(self.__runtime_log)
66 os.makedirs(self.__runtime_log_sub)
67
68 def add_project(self, project):
69 self.__projects.append(project)
70
71 def remove_project(self, project):
72 self.__projects.remove(project)
73
74 def subprocess_signal(self, subprocess_uuid, signal):
75 self.__subproces[subprocess_uuid].send_signal(signal)
76
77 def subprocess_terminate(self, subprocess_uuid, timeout=60, check_return=True):
78 process = self.__subprocess[subprocess_uuid]
79 process.terminate()
80 try:
81 process.wait(timeout)
82 except subprocess.TimeoutExpired:
83 # Force kill
84 return self.subprocess_kill(subprocess_uuid)
85 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
86 stdout.close()
87 stderr.close()
88 if check_return:
89 if process.returncode != 0:
90 raise subprocess.CalledProcessError(process.returncode, process.args)
91 return process
92
93 def subprocess_kill(self, subprocess_uuid):
94 process = self.__subprocess[subprocess_uuid]
95 process.kill()
96 process.wait()
97 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
98 stdout.close()
99 stderr.close()
100 return process
101
102 def subprocess_wait(self, subprocess_uuid, check_return=True):
103 process = self.__subprocess[subprocess_uuid]
104 process.wait()
105 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
106 stdout.close()
107 stderr.close()
108 if check_return:
109 if process.returncode != 0:
110 raise subprocess.CalledProcessError(process.returncode, process.args)
111 return process
112
113 def get_subprocess_stdout_path(self, subprocess_uuid):
114 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
115 return stdout.name
116
117 def get_subprocess_stderr_path(self, subprocess_uuid):
118 stdout, stderr = self.__stdout_stderr[subprocess_uuid]
119 return stderr.name
120
121 def spawn_subprocess(self, command_line, cwd=None):
122 args = shlex.split(command_line)
123 env = self.get_env()
124
125 if not os.path.isdir(self.lttng_home):
126 raise Exception("lttng home does not exist")
127
128 tmp_id = uuid.uuid1()
129 out_path = os.path.join(self.__runtime_log_sub, str(tmp_id) + ".out")
130 err_path = os.path.join(self.__runtime_log_sub, str(tmp_id) + ".err")
131
132 stdout = open(out_path, 'w')
133 stderr = open(err_path, 'w')
134
135 env_path = os.path.join(self.__runtime_log_sub, str(tmp_id) + ".env")
136 with open(env_path, 'w') as env_out:
137 pprint.pprint(env, stream=env_out)
138
139 p = subprocess.Popen(args, stdout=stdout, stderr=stderr, env=env, cwd=cwd)
140 self.__subprocess[tmp_id] = p
141 self.__stdout_stderr[tmp_id] = (stdout, stderr)
142 _logger.debug("Spawned sub pid: {} args: {} stdout: {} stderr{}".format(p.pid, p.args, out_path, err_path))
143 return tmp_id
144
145 def run(self, command_line, cwd=None, check_return=True, ld_preload="", classpath="", timeout=None):
146 """
147 Run the command and return a tuple of a (CompletedProcess, stdout_path,
148 stderr_path). The subprocess is already executed and returned. The
149 callecaller is responsible for checking for errors.
150 """
151 args = shlex.split(command_line)
152 env = self.get_env()
153
154 if ld_preload:
155 env['LD_PRELOAD'] = ld_preload
156 if classpath:
157 env['CLASSPATH'] = classpath
158
159
160 tmp_id = self._run_command_count
161 self._run_command_count += 1
162
163 cmd_map = os.path.join(self.__runtime_log, "cmd.map")
164 with open(cmd_map, 'a') as out:
165 out.write("{}: {}\n".format(tmp_id, args))
166
167 out_path = os.path.join(self.__runtime_log, str(tmp_id) + ".out")
168 err_path = os.path.join(self.__runtime_log, str(tmp_id) + ".err")
169 stdout = open(out_path, "w")
170 stderr = open(err_path, "w")
171
172 env_path = os.path.join(self.__runtime_log, str(tmp_id) + ".env")
173 with open(env_path, 'w') as env_out:
174 for key, value in env.items():
175 env_out.write('{}={}\n'.format(key, value))
176
177 cp = subprocess.run(args, stdout=stdout, stderr=stderr, env=env,
178 cwd=cwd, timeout=timeout)
179 _logger.debug("Command #{} args: {} stdout: {} stderr{}".format(tmp_id, cp.args, out_path, err_path))
180
181 # Add to the global log file. This can help a little. Leave the other
182 # file available for per-run analysis
183 with open(self._runtime_log_aggregation, "a") as log:
184 with open(out_path, "r") as out:
185 log.write("Output for command #{} {}\n".format(tmp_id, command_line))
186 log.write("Start >>>>>>>>>>>>>>>>\n")
187 log.write(out.read())
188 log.write("End <<<<<<<<<<<<<<<<\n")
189 with open(err_path, "r") as out:
190 log.write("Error for command #{} {}\n".format(tmp_id, command_line))
191 log.write("Start >>>>>>>>>>>>>>>>\n")
192 log.write(out.read())
193 log.write("End <<<<<<<<<<<<<<<<\n")
194
195 if check_return:
196 cp.check_returncode()
197
198 return (cp, out_path, err_path)
199
200 def get_cppflags(self):
201 cppflags = []
202 for project in self.__projects:
203 cppflags.append(project.get_cppflags())
204 return " ".join(cppflags)
205
206 def get_ldflags(self):
207 ldflags = []
208 for project in self.__projects:
209 ldflags.append(project.get_ldflags())
210 return " ".join(ldflags)
211
212 def get_ld_library_path(self):
213 library_path = []
214 for project in self.__projects:
215 library_path.append(project.get_ld_library_path())
216 return ":".join(library_path)
217
218 def get_bin_path(self):
219 path = []
220 for project in self.__projects:
221 path.append(project.get_bin_path())
222 return ":".join(path)
223
224 def get_env(self):
225 env = os.environ.copy()
226
227 env["LTTNG_HOME"] = self.lttng_home
228
229 env_fetch = {"CPPFLAGS": (self.get_cppflags(), " "),
230 "LDFLAGS": (self.get_ldflags(), " "),
231 "LD_LIBRARY_PATH": (self.get_ld_library_path(), ":"),
232 "PATH": (self.get_bin_path(), ":"),
233 }
234 for key, (value, delimiter) in env_fetch.items():
235 tmp_var = ""
236 if key in env:
237 tmp_var = env[key]
238 env[key] = delimiter.join([value, tmp_var])
239
240 for var, value in self.special_env_variables.items():
241 if var in env:
242 # Raise for now since no special cases is known
243 _logger.warning("% Special var % is already defined",
244 self.label, var)
245 raise Exception("Multiple definition of a special environment variable")
246 else:
247 env[var] = value
248
249 for project in self.__projects:
250 for var, value in project.special_env_variables.items():
251 if var in env:
252 # Raise for now since no special cases is known
253 _logger.warning("% Special var % is already defined",
254 self.label, var)
255 raise Exception("Multiple definition of a special environment variable")
256 else:
257 env[var] = value
258 return env
259
260 def load_test_module(self):
261 # Base directory is provided by env
262 self.run("modprobe lttng-test")
263 self._is_test_modules_loaded = True
264
265 def unload_test_module(self, check_return=True):
266 # Base directory is provided by env
267 if self._is_test_modules_loaded:
268 self.run("modprobe -r lttng-test", check_return=check_return)
269
270 def close(self):
271 for key, subp in self.__subprocess.items():
272 self.subprocess_kill(key)
273
274 # Always try to remove test module but do not perform check on return
275 # value.
276 self.unload_test_module(False)
277
278 # Copy the lttng_home used at runtime using hardlink to prevent useless
279 # data duplication
280 shutil.copytree(self.lttng_home, self.__post_runtime_lttng_home_path, copy_function=os.link)
This page took 0.037831 seconds and 5 git commands to generate.