blob: 2ae423d07a32d1620a35cf78c04572be9e0a8297 [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 Freytherdbb16e82019-04-30 22:03:12 +010088 lua_support = os.path.join(os.path.dirname(__file__), 'lua')
89 self._cfg = {
90 'test': {
91 'event_path': self._ev_server_path,
92 'lua_support': lua_support,
93 }
94 }
95
Holger Hans Peter Freyther8ae38a62018-08-29 04:25:30 +010096 def imsi(self):
97 return self._imsi
98
99 def ki(self):
100 return self._ki
101
Holger Hans Peter Freytherdbb16e82019-04-30 22:03:12 +0100102 def set_cfg_item(self, key, value):
103 """
104 Sets `key` to `value` inside the test dictionary.
105
106 Used by testcases to pass per MS settings into the lua script
107 generator.
108 """
109 self._cfg['test'][key] = value
110
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000111 def write_lua_cfg(self):
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000112 lua_cfg_file = os.path.join(self._tmp_dir, "lua_" + self._name_number + ".lua")
Holger Hans Peter Freytherdbb16e82019-04-30 22:03:12 +0100113 lua_script = template.render(self._lua_template, self._cfg)
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000114 with open(lua_cfg_file, 'w') as w:
115 w.write(lua_script)
116 return lua_cfg_file
117
118 def write_mob_cfg(self, lua_filename, phy_filename):
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000119 cfg = {
120 'test': {
121 'script': lua_filename,
122 'virt_phy': phy_filename,
Holger Hans Peter Freythera7b61f32018-08-28 11:00:11 +0100123 'imsi': self._imsi,
124 'ki_comp128': self._ki,
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000125 'ms_number': self._name_number,
126 }
127 }
128 mob_cfg_file = os.path.join(self._tmp_dir, "mob_" + self._name_number + ".cfg")
129 mob_vty = template.render(self._cfg_template, cfg)
130 with open(mob_cfg_file, 'w') as w:
131 w.write(mob_vty)
132 return mob_cfg_file
133
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +0000134 def start(self, loop, suite_run=None):
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000135 lua_filename = self.write_lua_cfg()
136 mob_filename = self.write_mob_cfg(lua_filename, self._phy_filename)
137
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +0000138 self.log("Starting mobile")
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000139 # Let the kernel pick an unused port for the VTY.
Holger Hans Peter Freyther82feeff2018-12-16 14:34:09 +0000140 args = [self._binary, "-c", mob_filename]
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +0000141 self._omob_proc = process.Process(self.name(), self._tmp_dir,
142 args, env=self._env)
143 if suite_run:
144 suite_run.remember_to_stop(self._omob_proc)
145 self._omob_proc.launch()
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000146
Holger Hans Peter Freytherf743afb2018-11-05 06:07:57 +0000147 def terminate(self):
Holger Hans Peter Freytherff19a5e2018-02-25 21:29:35 +0000148 """Clean up things."""
149 if self._omob_proc:
Holger Hans Peter Freytherb5e86b72018-11-05 03:43:27 +0000150 self._omob_proc.terminate()
Holger Hans Peter Freytherf16a8f72019-04-30 12:01:57 +0100151
152
153class MobileTestStarter(log.Origin):
154 """
155 A test to launch a configurable amount of MS and make them
156 execute a Location Updating Procedure.
157
158 Configure the number of MS to be tested and a function that
159 decides how quickly to start them and a timeout.
160 """
161
162 TEMPLATE_LUA = "osmo-mobile.lua"
163 TEMPLATE_CFG = "osmo-mobile.cfg"
164
165 def __init__(self, name, options, cdf_function,
166 event_server, tmp_dir, results, suite_run=None):
167 super().__init__(log.C_RUN, name)
168 self._binary_options = options
169 self._cdf = cdf_function
170 self._suite_run = suite_run
171 self._tmp_dir = tmp_dir
172 self._event_server = event_server
173 self._results = results
174 self._unstarted = []
175 self._mobiles = []
176 self._phys = []
177
178 self._started = []
179 self._subscribers = []
180
181 self._event_server.register(self.handle_msg)
182
183 def subscriber_add(self, subscriber):
184 """
185 Adds a subscriber to the list of subscribers.
186
187 Must be called before starting the testcase.
188 """
189 self._subscribers.append(subscriber)
190
191 def configure_tasks(self):
192 """Sets up the test run."""
193
194 self._cdf.set_target(len(self._subscribers))
195 self._outstanding = len(self._subscribers)
196 for i in range(0, self._outstanding):
197 ms_name = "%.5d" % i
198
199 phy = OsmoVirtPhy(self._binary_options.virtphy,
200 self._binary_options.env,
201 ms_name, self._tmp_dir)
202 self._phys.append(phy)
203
204 launcher = OsmoMobile(self._binary_options.mobile,
205 self._binary_options.env,
206 ms_name, self._tmp_dir, self.TEMPLATE_LUA,
207 self.TEMPLATE_CFG, self._subscribers[i],
208 phy.phy_filename(),
209 self._event_server.server_path())
210 self._results[ms_name] = ResultStore(ms_name)
211 self._mobiles.append(launcher)
212 self._unstarted = copy(self._mobiles)
213
214 def pre_launch(self, loop):
215 """
216 We need the virtphy's be ready when the lua script in the
217 mobile comes and kicks-off the test. In lua we don't seem to
218 be able to just stat/check if a file/socket exists so we need
219 to do this from here.
220 """
221 self.log("Pre-launching all virtphy's")
222 for phy in self._phys:
223 phy.start(loop, self._suite_run)
224
225 self.log("Checking if sockets are in the filesystem")
226 for phy in self._phys:
227 phy.verify_ready()
228
229 def prepare(self, loop):
230 self.log("Starting testcase")
231
Holger Hans Peter Freytherf16a8f72019-04-30 12:01:57 +0100232 self.pre_launch(loop)
233
234 self._start_time = time.clock_gettime(time.CLOCK_MONOTONIC)
235 self._end_time = self._start_time + \
236 self._cdf.duration().total_seconds() + \
237 timedelta(seconds=120).total_seconds()
238
239 self._started = []
240 self._too_slow = 0
241
242 def step_once(self, loop, current_time):
243 if len(self._unstarted) <= 0:
244 return current_time, None
245
246 step_size = self._cdf.step_size().total_seconds()
247
248 # Start
249 self._cdf.step_once()
250
251 # Check for timeout
252 # start pending MS
253 while len(self._started) < self._cdf.current_scaled_value() and len(self._unstarted) > 0:
254 ms = self._unstarted.pop(0)
255 ms.start(loop, self._suite_run)
256 launch_time = time.clock_gettime(time.CLOCK_MONOTONIC)
257 self._results[ms.name_number()].set_launch_time(launch_time)
258 self._started.append(ms)
259
260 now_time = time.clock_gettime(time.CLOCK_MONOTONIC)
261 sleep_time = (current_time + step_size) - now_time
262 if sleep_time <= 0:
263 self.log("Starting too slowly. Moving on",
264 target=(current_time + step_size), now=now_time, sleep=sleep_time)
265 self._too_slow += 1
266 sleep_time = 0
267
268 if len(self._unstarted) == 0:
269 end_time = time.clock_gettime(time.CLOCK_MONOTONIC)
270 self.log("All started...", too_slow=self._too_slow, duration=end_time - self._start_time)
271 return current_time, None
272
273 return current_time + step_size, sleep_time
274
275 def start_all(self, loop, test_duration):
276 """
277 Starts all processes according to the schedule set by the CDF.
278 """
279 self.prepare(loop)
280
281 self._to_complete_time = self._start_time + test_duration.total_seconds()
282 tick_time = self._start_time
283
284 while len(self._unstarted) > 0:
285 tick_time, sleep_time = self.step_once(loop, tick_time)
286 now_time = time.clock_gettime(time.CLOCK_MONOTONIC)
287 if sleep_time is None:
288 sleep_time = self._to_complete_time - now_time
289 if sleep_time < 0:
290 break
291 loop.schedule_timeout(sleep_time)
292 loop.select()
293 return self._to_complete_time
294
295 def stop_all(self):
296 for launcher in self._started:
297 launcher.terminate()
298
299 def handle_msg(self, _data, addr, time):
300 data = json.loads(_data.decode())
301
302 if data['type'] == 'register':
303 ms = self._results[data['ms']]
304 ms.set_start_time(time)
305 launch_delay = ms.start_time() - ms.launch_time()
306 self.log("MS start registered ", ms=ms, at=time, delay=launch_delay)
Holger Hans Peter Freytherdbb16e82019-04-30 22:03:12 +0100307
308 def mobiles(self):
309 """Returns the list of mobiles configured."""
310 return self._mobiles