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