blob: 61d3bb07f783e9fe3be6c750e0f98bd015be94b5 [file] [log] [blame]
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +00001# osmo_ms_driver: Starter for processes
2# Help to start processes over time.
3#
4# Copyright (C) 2018 by Holger Hans Peter Freyther
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as
8# published by the Free Software Foundation, either version 3 of the
9# License, or (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +000019from osmo_gsm_tester import log, process, template
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000020
Holger Hans Peter Freytherf16a8f72019-04-30 12:01:57 +010021from .test_support import ResultStore
22
23from copy import copy
24from datetime import timedelta
25
Holger Hans Peter Freytherf658b832018-11-05 05:05:43 +000026import collections
Holger Hans Peter Freytherf16a8f72019-04-30 12:01:57 +010027import json
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000028import os
29import os.path
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000030import time
31
Holger Hans Peter Freytherf658b832018-11-05 05:05:43 +000032BinaryOptions = collections.namedtuple("BinaryOptions", ["virtphy", "mobile", "env"])
33
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000034class Launcher(log.Origin):
Holger Hans Peter Freytherf658b832018-11-05 05:05:43 +000035 def __init__(self, binary, env, base_name, name_number, tmp_dir):
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000036 super().__init__(log.C_RUN, "{}/{}".format(base_name, name_number))
Holger Hans Peter Freytherf658b832018-11-05 05:05:43 +000037 self._binary = binary
38 self._env = env
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000039 self._name_number = name_number
Holger Hans Peter Freyther792614f2018-11-05 06:23:39 +000040 self._tmp_dir = tmp_dir.new_dir(self.name())
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000041
42 def name_number(self):
43 return self._name_number
44
45class OsmoVirtPhy(Launcher):
Holger Hans Peter Freytherf658b832018-11-05 05:05:43 +000046 def __init__(self, binary, env, name_number, tmp_dir):
47 super().__init__(binary, env, "osmo-ms-virt-phy", name_number, tmp_dir)
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000048 self._phy_filename = os.path.join(self._tmp_dir, "osmocom_l2_" + self._name_number)
Holger Hans Peter Freytherb5e86b72018-11-05 03:43:27 +000049 self._vphy_proc = None
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000050
51 def phy_filename(self):
52 return self._phy_filename
53
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +000054 def start(self, loop, suite_run=None):
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000055 if len(self._phy_filename.encode()) > 107:
56 raise log.Error('Path for unix socket is longer than max allowed len for unix socket path (107):', self._phy_filename)
57
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +000058 self.log("Starting virtphy")
Holger Hans Peter Freytherf658b832018-11-05 05:05:43 +000059 args = [self._binary, "--l1ctl-sock=" + self._phy_filename]
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +000060 self._vphy_proc = process.Process(self.name(), self._tmp_dir,
61 args, env=self._env)
62 if suite_run:
63 suite_run.remember_to_stop(self._vphy_proc)
64 self._vphy_proc.launch()
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000065
66 def verify_ready(self):
67 while True:
68 if os.path.exists(self._phy_filename):
69 return
70 time.sleep(0.2)
71
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +000072 def terminate(self):
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000073 """Clean up things."""
74 if self._vphy_proc:
Holger Hans Peter Freytherb5e86b72018-11-05 03:43:27 +000075 self._vphy_proc.terminate()
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000076
77class OsmoMobile(Launcher):
Holger Hans Peter Freyther5e67ed42019-02-25 09:48:50 +000078 def __init__(self, binary, env, name_number, tmp_dir, lua_tmpl, cfg_tmpl, subscriber, phy_filename, ev_server_path):
Holger Hans Peter Freytherf658b832018-11-05 05:05:43 +000079 super().__init__(binary, env, "osmo-ms-mob", name_number, tmp_dir)
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000080 self._lua_template = lua_tmpl
81 self._cfg_template = cfg_tmpl
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000082 self._phy_filename = phy_filename
83 self._ev_server_path = ev_server_path
Holger Hans Peter Freyther5e67ed42019-02-25 09:48:50 +000084 self._imsi = subscriber.imsi()
85 self._ki = subscriber.ki()
Holger Hans Peter Freytherb5e86b72018-11-05 03:43:27 +000086 self._omob_proc = None
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000087
Holger Hans Peter Freyther8ae38a62018-08-29 04:25:30 +010088 def imsi(self):
89 return self._imsi
90
91 def ki(self):
92 return self._ki
93
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +000094 def write_lua_cfg(self):
95 lua_support = os.path.join(os.path.dirname(__file__), 'lua')
96 cfg = {
97 'test': {
98 'event_path': self._ev_server_path,
99 'lua_support': lua_support,
Holger Hans Peter Freythere524f2c2019-04-27 15:44:44 +0100100 'run_lu_test': True,
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000101 }
102 }
103 lua_cfg_file = os.path.join(self._tmp_dir, "lua_" + self._name_number + ".lua")
104 lua_script = template.render(self._lua_template, cfg)
105 with open(lua_cfg_file, 'w') as w:
106 w.write(lua_script)
107 return lua_cfg_file
108
109 def write_mob_cfg(self, lua_filename, phy_filename):
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000110 cfg = {
111 'test': {
112 'script': lua_filename,
113 'virt_phy': phy_filename,
Holger Hans Peter Freythera7b61f32018-08-28 11:00:11 +0100114 'imsi': self._imsi,
115 'ki_comp128': self._ki,
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000116 'ms_number': self._name_number,
117 }
118 }
119 mob_cfg_file = os.path.join(self._tmp_dir, "mob_" + self._name_number + ".cfg")
120 mob_vty = template.render(self._cfg_template, cfg)
121 with open(mob_cfg_file, 'w') as w:
122 w.write(mob_vty)
123 return mob_cfg_file
124
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +0000125 def start(self, loop, suite_run=None):
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000126 lua_filename = self.write_lua_cfg()
127 mob_filename = self.write_mob_cfg(lua_filename, self._phy_filename)
128
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +0000129 self.log("Starting mobile")
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000130 # Let the kernel pick an unused port for the VTY.
Holger Hans Peter Freyther82feeff2018-12-16 14:34:09 +0000131 args = [self._binary, "-c", mob_filename]
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +0000132 self._omob_proc = process.Process(self.name(), self._tmp_dir,
133 args, env=self._env)
134 if suite_run:
135 suite_run.remember_to_stop(self._omob_proc)
136 self._omob_proc.launch()
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000137
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +0000138 def terminate(self):
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000139 """Clean up things."""
140 if self._omob_proc:
Holger Hans Peter Freytherb5e86b72018-11-05 03:43:27 +0000141 self._omob_proc.terminate()
Holger Hans Peter Freytherf16a8f72019-04-30 12:01:57 +0100142
143
144class MobileTestStarter(log.Origin):
145 """
146 A test to launch a configurable amount of MS and make them
147 execute a Location Updating Procedure.
148
149 Configure the number of MS to be tested and a function that
150 decides how quickly to start them and a timeout.
151 """
152
153 TEMPLATE_LUA = "osmo-mobile.lua"
154 TEMPLATE_CFG = "osmo-mobile.cfg"
155
156 def __init__(self, name, options, cdf_function,
157 event_server, tmp_dir, results, suite_run=None):
158 super().__init__(log.C_RUN, name)
159 self._binary_options = options
160 self._cdf = cdf_function
161 self._suite_run = suite_run
162 self._tmp_dir = tmp_dir
163 self._event_server = event_server
164 self._results = results
165 self._unstarted = []
166 self._mobiles = []
167 self._phys = []
168
169 self._started = []
170 self._subscribers = []
171
172 self._event_server.register(self.handle_msg)
173
174 def subscriber_add(self, subscriber):
175 """
176 Adds a subscriber to the list of subscribers.
177
178 Must be called before starting the testcase.
179 """
180 self._subscribers.append(subscriber)
181
182 def configure_tasks(self):
183 """Sets up the test run."""
184
185 self._cdf.set_target(len(self._subscribers))
186 self._outstanding = len(self._subscribers)
187 for i in range(0, self._outstanding):
188 ms_name = "%.5d" % i
189
190 phy = OsmoVirtPhy(self._binary_options.virtphy,
191 self._binary_options.env,
192 ms_name, self._tmp_dir)
193 self._phys.append(phy)
194
195 launcher = OsmoMobile(self._binary_options.mobile,
196 self._binary_options.env,
197 ms_name, self._tmp_dir, self.TEMPLATE_LUA,
198 self.TEMPLATE_CFG, self._subscribers[i],
199 phy.phy_filename(),
200 self._event_server.server_path())
201 self._results[ms_name] = ResultStore(ms_name)
202 self._mobiles.append(launcher)
203 self._unstarted = copy(self._mobiles)
204
205 def pre_launch(self, loop):
206 """
207 We need the virtphy's be ready when the lua script in the
208 mobile comes and kicks-off the test. In lua we don't seem to
209 be able to just stat/check if a file/socket exists so we need
210 to do this from here.
211 """
212 self.log("Pre-launching all virtphy's")
213 for phy in self._phys:
214 phy.start(loop, self._suite_run)
215
216 self.log("Checking if sockets are in the filesystem")
217 for phy in self._phys:
218 phy.verify_ready()
219
220 def prepare(self, loop):
221 self.log("Starting testcase")
222
223 self.configure_tasks()
224 self.pre_launch(loop)
225
226 self._start_time = time.clock_gettime(time.CLOCK_MONOTONIC)
227 self._end_time = self._start_time + \
228 self._cdf.duration().total_seconds() + \
229 timedelta(seconds=120).total_seconds()
230
231 self._started = []
232 self._too_slow = 0
233
234 def step_once(self, loop, current_time):
235 if len(self._unstarted) <= 0:
236 return current_time, None
237
238 step_size = self._cdf.step_size().total_seconds()
239
240 # Start
241 self._cdf.step_once()
242
243 # Check for timeout
244 # start pending MS
245 while len(self._started) < self._cdf.current_scaled_value() and len(self._unstarted) > 0:
246 ms = self._unstarted.pop(0)
247 ms.start(loop, self._suite_run)
248 launch_time = time.clock_gettime(time.CLOCK_MONOTONIC)
249 self._results[ms.name_number()].set_launch_time(launch_time)
250 self._started.append(ms)
251
252 now_time = time.clock_gettime(time.CLOCK_MONOTONIC)
253 sleep_time = (current_time + step_size) - now_time
254 if sleep_time <= 0:
255 self.log("Starting too slowly. Moving on",
256 target=(current_time + step_size), now=now_time, sleep=sleep_time)
257 self._too_slow += 1
258 sleep_time = 0
259
260 if len(self._unstarted) == 0:
261 end_time = time.clock_gettime(time.CLOCK_MONOTONIC)
262 self.log("All started...", too_slow=self._too_slow, duration=end_time - self._start_time)
263 return current_time, None
264
265 return current_time + step_size, sleep_time
266
267 def start_all(self, loop, test_duration):
268 """
269 Starts all processes according to the schedule set by the CDF.
270 """
271 self.prepare(loop)
272
273 self._to_complete_time = self._start_time + test_duration.total_seconds()
274 tick_time = self._start_time
275
276 while len(self._unstarted) > 0:
277 tick_time, sleep_time = self.step_once(loop, tick_time)
278 now_time = time.clock_gettime(time.CLOCK_MONOTONIC)
279 if sleep_time is None:
280 sleep_time = self._to_complete_time - now_time
281 if sleep_time < 0:
282 break
283 loop.schedule_timeout(sleep_time)
284 loop.select()
285 return self._to_complete_time
286
287 def stop_all(self):
288 for launcher in self._started:
289 launcher.terminate()
290
291 def handle_msg(self, _data, addr, time):
292 data = json.loads(_data.decode())
293
294 if data['type'] == 'register':
295 ms = self._results[data['ms']]
296 ms.set_start_time(time)
297 launch_delay = ms.start_time() - ms.launch_time()
298 self.log("MS start registered ", ms=ms, at=time, delay=launch_delay)