Commit | Line | Data |
---|---|---|
18aedaf9 JR |
1 | import os |
2 | import shlex | |
3 | import subprocess | |
4 | import uuid | |
5 | import logging | |
6 | ||
7 | _logger = logging.getLogger("Runtime") | |
8 | ||
9 | ||
10 | class Runtime(object): | |
11 | def __init__(self, runtime_dir): | |
12 | """ | |
13 | A dictionary of popen object eg. lttng-sessiond, relayd, | |
14 | anything really. Key is a uuid. | |
15 | """ | |
16 | self.__subprocess = {} | |
17 | self.__stdout_stderr = {} | |
18 | self.__projects = [] | |
19 | ||
20 | self.__runtime_log = os.path.join(runtime_dir, "log") | |
21 | self.__runtime_log_sub = os.path.join(self.__runtime_log, "subprocess") | |
22 | ||
23 | self._runtime_log_aggregation = os.path.join(self.__runtime_log, "runtime.log") | |
24 | ||
25 | self._run_command_count = 0 | |
26 | ||
27 | self.lttng_home = os.path.join(runtime_dir, "lttng_home") | |
28 | ||
29 | # TODO move exist_ok to false !!!! ONLY for testing | |
30 | os.makedirs(self.__runtime_log, exist_ok=True) | |
31 | os.makedirs(self.__runtime_log_sub, exist_ok=True) | |
32 | os.makedirs(self.lttng_home, exist_ok=True) | |
33 | ||
34 | def add_project(self, project): | |
35 | self.__projects.append(project) | |
36 | ||
37 | def subprocess_signal(self, subprocess_uuid, signal): | |
38 | self.__subproces[subprocess_uuid].send_signal(signal) | |
39 | ||
40 | def subprocess_terminate(self, subprocess_uuid, timeout=60): | |
41 | process = self.__subprocess[subprocess_uuid] | |
42 | process.terminate() | |
43 | process.wait(timeout) | |
44 | stdout, stderr = self.__stdout_stderr[subprocess_uuid] | |
45 | stdout.close() | |
46 | stderr.close() | |
47 | ||
48 | def subprocess_kill(self, subprocess_uuid): | |
49 | process = self.__subprocess[subprocess_uuid] | |
50 | process.kill() | |
51 | process.wait() | |
52 | stdout, stderr = self.__stdout_stderr[subprocess_uuid] | |
53 | stdout.close() | |
54 | stderr.close() | |
55 | ||
56 | def get_subprocess_stdout_path(self, subprocess_uuid): | |
57 | stdout, stderr = self.__stdout_stderr[subprocess_uuid] | |
58 | return stdout.name | |
59 | ||
60 | def get_subprocess_stderr_path(self, subprocess_uuid): | |
61 | stdout, stderr = self.__stdout_stderr[subprocess_uuid] | |
62 | return stderr.name | |
63 | ||
64 | def spawn_subprocess(self, command_line): | |
65 | args = shlex.split(command_line) | |
66 | env = self.get_env() | |
67 | ||
68 | tmp_id = uuid.uuid1() | |
69 | out_path = os.path.join(self.__runtime_log_sub, str(tmp_id) + ".out") | |
70 | err_path = os.path.join(self.__runtime_log_sub, str(tmp_id) + ".err") | |
71 | stdout = open(out_path, "w") | |
72 | stderr = open(err_path, "w") | |
73 | ||
74 | p = subprocess.Popen(args, stdout=stdout, stderr=stderr, env=env) | |
75 | self.__subprocess[tmp_id] = p | |
76 | self.__stdout_stderr[tmp_id] = (stdout, stderr) | |
77 | _logger.debug("Spawned sub pid: {} args: {} stdout: {} stderr{}".format(p.pid, p.args, out_path, err_path)) | |
78 | ||
79 | def run(self, command_line): | |
80 | """ | |
81 | Run the command and return a tuple of a (CompletedProcess, stdout_path, | |
82 | stderr_path). The subprocess is already executed and returned. The | |
83 | callecaller is responsible for checking for errors. | |
84 | """ | |
85 | args = shlex.split(command_line) | |
86 | env = self.get_env() | |
87 | ||
88 | tmp_id = self._run_command_count | |
89 | self._run_command_count += 1 | |
90 | ||
91 | out_path = os.path.join(self.__runtime_log, str(tmp_id) + ".out") | |
92 | err_path = os.path.join(self.__runtime_log, str(tmp_id) + ".err") | |
93 | stdout = open(out_path, "w") | |
94 | stderr = open(err_path, "w") | |
95 | ||
96 | stdout.write("Output for command #{} {}\n".format(tmp_id, command_line)) | |
97 | stdout.write("Start >>>>>>>>>>>>>>>>\n") | |
98 | stdout.flush() | |
99 | ||
100 | stderr.write("Output for command #{} {}\n".format(tmp_id, command_line)) | |
101 | stderr.write("Start >>>>>>>>>>>>>>>>\n") | |
102 | stderr.flush() | |
103 | ||
104 | cp = subprocess.run(args, stdout=stdout, stderr=stderr, env=env) | |
105 | _logger.debug("Command #{} args: {} stdout: {} stderr{}".format(tmp_id, cp.args, out_path, err_path)) | |
106 | ||
107 | stdout.write("End <<<<<<<<<<<<<<<<\n") | |
108 | stdout.close() | |
109 | ||
110 | stderr.write("End <<<<<<<<<<<<<<<<\n") | |
111 | stderr.close() | |
112 | ||
113 | # Add to the global log file. This can help a little. Leave the other | |
114 | # file available for per-run analysis | |
115 | with open(self._runtime_log_aggregation, "a") as log: | |
116 | with open(out_path, "r") as out: | |
117 | log.write(out.read()) | |
118 | with open(err_path, "r") as out: | |
119 | log.write(out.read()) | |
120 | ||
121 | return (cp, out_path, err_path) | |
122 | ||
123 | def get_cppflags(self): | |
124 | cppflags = [] | |
125 | for project in self.__projects: | |
126 | cppflags.append(project.get_cppflags()) | |
127 | return " ".join(cppflags) | |
128 | ||
129 | def get_ldflags(self): | |
130 | ldflags = [] | |
131 | for project in self.__projects: | |
132 | ldflags.append(project.get_ldflags()) | |
133 | return " ".join(ldflags) | |
134 | ||
135 | def get_ld_library_path(self): | |
136 | library_path = [] | |
137 | for project in self.__projects: | |
138 | library_path.append(project.get_ld_library_path()) | |
139 | return " ".join(library_path) | |
140 | ||
141 | def get_bin_path(self): | |
142 | path = [] | |
143 | for project in self.__projects: | |
144 | path.append(project.get_bin_path()) | |
145 | return ":".join(path) | |
146 | ||
147 | def get_env(self): | |
148 | env = os.environ.copy() | |
149 | ||
150 | env["LTTNG_HOME"] = self.lttng_home | |
151 | ||
152 | env_fetch = {"CPPFLAGS": (self.get_cppflags(), " "), | |
153 | "LDFLAGS": (self.get_ldflags(), " "), | |
154 | "LD_LIRABRY_PATH": (self.get_ld_library_path(), ":"), | |
155 | "PATH": (self.get_bin_path(), ":"), | |
156 | } | |
157 | for key, (value, delimiter) in env_fetch.items(): | |
158 | tmp_var = "" | |
159 | if key in env: | |
160 | tmp_var = env[key] | |
161 | env[key] = delimiter.join([value, tmp_var]) | |
162 | ||
163 | for project in self.__projects: | |
164 | for var, value in project.special_env_variables.items(): | |
165 | if var in env: | |
166 | # Raise for now since no special cases is known | |
167 | _logger.warning("% Special var % is already defined", | |
168 | self.label, var) | |
169 | raise Exception("Multiple definition of a special environment variable") | |
170 | else: | |
171 | env[var] = value | |
172 | return env | |
173 | ||
174 | def close(self): | |
175 | for key, subp in self.__subprocess.items(): | |
176 | subp.terminate() | |
177 | for key, subp in self.__subprocess.items(): | |
178 | try: | |
179 | # TODO move timeout to settings | |
180 | subp.wait(timeout=60) | |
181 | except subprocess.TimeoutExpired as e: | |
182 | # Force a little bit | |
183 | subp.kill() | |
184 | subp.wait() | |
185 | for key, (stdout, stderr) in self.__stdout_stderr.items(): | |
186 | stdout.close() | |
187 | stderr.close() |