Neels Hofmeyr | de6743d | 2020-11-27 08:24:56 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | from osmo_gsm_tester.testenv import * |
| 3 | from osmo_gsm_tester.obj.osmo_ctrl import * |
| 4 | |
| 5 | hlr = tenv.hlr() |
| 6 | bts0 = tenv.bts() |
| 7 | bts1 = tenv.bts() |
| 8 | mgw_msc = tenv.mgw() |
| 9 | mgw_bsc = tenv.mgw() |
| 10 | stp = tenv.stp() |
| 11 | msc = tenv.msc(hlr, mgw_msc, stp) |
| 12 | bsc = tenv.bsc(msc, mgw_bsc, stp) |
| 13 | ms0 = tenv.modem() |
| 14 | ms1 = tenv.modem() |
| 15 | |
| 16 | hlr.start() |
| 17 | stp.start() |
| 18 | msc.start() |
| 19 | mgw_msc.start() |
| 20 | mgw_bsc.start() |
| 21 | bsc.bts_add(bts0) |
| 22 | bsc.bts_add(bts1) |
| 23 | bsc.start() |
| 24 | |
| 25 | # prevent handovers from measurement reports, enable handover so that |
| 26 | # triggering handover from VTY works. |
| 27 | bsc.vty.cmds( |
| 28 | 'enable', |
| 29 | 'configure terminal', |
| 30 | 'network', |
| 31 | 'handover algorithm 2', |
| 32 | 'handover2 min rxlev -110', |
| 33 | 'handover2 min rxqual 7', |
| 34 | 'handover2 power budget hysteresis 999', |
| 35 | 'handover 1', |
| 36 | 'end') |
| 37 | # now back on the 'enable' node. |
| 38 | |
| 39 | # first start only the first BTS, to make sure both modems subscribe there |
| 40 | with test.report_fragment('01_bts0_started'): |
| 41 | bts0.start() |
| 42 | wait(bsc.bts_is_connected, bts0) |
| 43 | |
| 44 | hlr.subscriber_add(ms0) |
| 45 | hlr.subscriber_add(ms1) |
| 46 | |
| 47 | ms0.connect(msc.mcc_mnc()) |
| 48 | ms1.connect(msc.mcc_mnc()) |
| 49 | |
| 50 | ms0.log_info() |
| 51 | ms1.log_info() |
| 52 | |
| 53 | print('waiting for modems to attach...') |
| 54 | |
| 55 | with test.report_fragment('02.1_ms0_attach'): |
| 56 | wait(ms0.is_registered, msc.mcc_mnc()) |
| 57 | |
| 58 | with test.report_fragment('02.2_ms1_attach'): |
| 59 | wait(ms1.is_registered, msc.mcc_mnc()) |
| 60 | |
| 61 | with test.report_fragment('02.3_subscribed_in_msc'): |
| 62 | wait(msc.subscriber_attached, ms0, ms1) |
| 63 | |
| 64 | assert len(ms0.call_id_list()) == 0 and len(ms1.call_id_list()) == 0 |
| 65 | mo_cid = ms0.call_dial(ms1) |
| 66 | mt_cid = ms1.call_wait_incoming(ms0) |
| 67 | print('dial success') |
| 68 | |
| 69 | with test.report_fragment('03_call_established'): |
| 70 | assert not ms0.call_is_active(mo_cid) and not ms1.call_is_active(mt_cid) |
| 71 | ms1.call_answer(mt_cid) |
| 72 | wait(ms0.call_is_active, mo_cid) |
| 73 | wait(ms1.call_is_active, mt_cid) |
| 74 | print('answer success, call established and ongoing') |
| 75 | |
| 76 | assert bsc.vty.active_lchans_match( |
| 77 | expected=('0-0-2-0 TCH/F ESTABLISHED', |
| 78 | '0-0-3-0 TCH/F ESTABLISHED')) |
| 79 | |
| 80 | # call is connected; start up the second BTS so that we can trigger a handover to it |
| 81 | with test.report_fragment('04.1_bts1_started'): |
| 82 | bts1.start() |
| 83 | wait(bsc.bts_is_connected, bts1) |
| 84 | |
| 85 | print('wait a bit for modems to see bts1') |
| 86 | sleep(10.0) |
| 87 | # TODO evaluate measurement reports instead? |
| 88 | |
| 89 | counter_names = ( |
| 90 | 'handover:completed', |
| 91 | 'handover:stopped', |
| 92 | 'handover:no_channel', |
| 93 | 'handover:timeout', |
| 94 | 'handover:failed', |
| 95 | 'handover:error', |
| 96 | ) |
| 97 | counters = RateCounters('bsc', counter_names, from_ctrl=bsc.ctrl) |
| 98 | counters.add(RateCounters('bts', counter_names, instances=(0, 1))) |
| 99 | |
| 100 | def do_handover(initial_lchans, target_lchan, vty_cmd, final_lchans, attempts=5): |
| 101 | worked = False |
| 102 | while (attempts > 0) and (not worked): |
| 103 | # make sure the call is still active as expected |
| 104 | assert bsc.vty.active_lchans_match(**initial_lchans) |
| 105 | # make sure the handover target lchan is unused (maybe waiting after previous error) |
| 106 | wait(bsc.vty.active_lchans_match, **target_lchan, timeout=20) |
| 107 | |
| 108 | counters.read() |
| 109 | log_mark = bsc.process.get_output_mark('stderr') |
| 110 | |
| 111 | print('trigger handover: %s' % vty_cmd) |
| 112 | bsc.vty.cmd(vty_cmd) |
| 113 | |
| 114 | print('wait for handover counters to change...') |
| 115 | wait(counters.changed, timeout=20) |
| 116 | print(counters.diff.str(skip_zero_vals=True)) |
| 117 | |
| 118 | print('\n'+'\n'.join(bsc.process.grep_output('stderr', r'\bhandover\(|\bDCHAN\b', log_mark))) |
| 119 | |
| 120 | worked = bsc.vty.active_lchans_match(**final_lchans) |
| 121 | if not worked and attempts > 0: |
| 122 | attempts -= 1 |
| 123 | print('did not work, try again... (attempts left: %d)' % attempts) |
| 124 | return worked |
| 125 | |
| 126 | |
| 127 | with test.report_fragment('05.1_handover_ms0'): |
| 128 | assert do_handover( |
| 129 | initial_lchans=dict( |
| 130 | expected=('0-0-2-0 TCH/F ESTABLISHED', |
| 131 | '0-0-3-0 TCH/F ESTABLISHED'), |
| 132 | ), |
| 133 | target_lchan=dict( |
| 134 | not_expected=('1-0-2-0',), |
| 135 | ), |
| 136 | vty_cmd='bts 0 trx 0 timeslot 2 sub-slot 0 handover 1', |
| 137 | final_lchans=dict( |
| 138 | expected=('0-0-3-0 TCH/F ESTABLISHED', |
| 139 | '1-0-2-0 TCH/F ESTABLISHED',), |
| 140 | not_expected=('0-0-2-0 TCH/F ESTABLISHED',), |
| 141 | ), |
| 142 | ) |
| 143 | |
| 144 | with test.report_fragment('05.2_handover_ms1'): |
| 145 | assert do_handover( |
| 146 | initial_lchans=dict( |
| 147 | expected=('0-0-3-0 TCH/F ESTABLISHED', |
| 148 | '1-0-2-0 TCH/F ESTABLISHED'), |
| 149 | ), |
| 150 | target_lchan=dict( |
| 151 | not_expected=('1-0-3-0',), |
| 152 | ), |
| 153 | vty_cmd='bts 0 trx 0 timeslot 3 sub-slot 0 handover 1', |
| 154 | final_lchans=dict( |
| 155 | expected=('1-0-2-0 TCH/F ESTABLISHED', |
| 156 | '1-0-3-0 TCH/F ESTABLISHED',), |
| 157 | not_expected=('0-0-2-0 TCH/F ESTABLISHED', |
| 158 | '0-0-3-0 TCH/F ESTABLISHED',), |
| 159 | ), |
| 160 | ) |
| 161 | |
| 162 | with test.report_fragment('06_call_stable'): |
| 163 | print('expect the call to continue for a while, to ensure the new lchan is functional') |
| 164 | for i in range(5): |
| 165 | sleep(5) |
| 166 | assert bsc.vty.active_lchans_match( |
| 167 | expected=('1-0-2-0 TCH/F ESTABLISHED', |
| 168 | '1-0-3-0 TCH/F ESTABLISHED',)) |
| 169 | print('call is still fine') |
| 170 | |
| 171 | print('handover back (test the other BTS model)') |
| 172 | |
| 173 | with test.report_fragment('07.1_handover_ms1_back'): |
| 174 | assert do_handover( |
| 175 | initial_lchans=dict( |
| 176 | expected=('1-0-2-0 TCH/F ESTABLISHED', |
| 177 | '1-0-3-0 TCH/F ESTABLISHED'), |
| 178 | ), |
| 179 | target_lchan=dict( |
| 180 | not_expected=('0-0-2-0',), |
| 181 | ), |
| 182 | vty_cmd='bts 1 trx 0 timeslot 3 sub-slot 0 handover 0', |
| 183 | final_lchans=dict( |
| 184 | expected=('0-0-2-0 TCH/F ESTABLISHED', |
| 185 | '1-0-2-0 TCH/F ESTABLISHED',), |
| 186 | not_expected=('1-0-3-0 TCH/F ESTABLISHED',), |
| 187 | ), |
| 188 | ) |
| 189 | |
| 190 | with test.report_fragment('07.2_handover_ms0_back'): |
| 191 | assert do_handover( |
| 192 | initial_lchans=dict( |
| 193 | expected=('0-0-2-0 TCH/F ESTABLISHED', |
| 194 | '1-0-2-0 TCH/F ESTABLISHED'), |
| 195 | ), |
| 196 | target_lchan=dict( |
| 197 | not_expected=('0-0-3-0',), |
| 198 | ), |
| 199 | vty_cmd='bts 1 trx 0 timeslot 2 sub-slot 0 handover 0', |
| 200 | final_lchans=dict( |
| 201 | expected=('0-0-2-0 TCH/F ESTABLISHED', |
| 202 | '0-0-3-0 TCH/F ESTABLISHED',), |
| 203 | not_expected=('1-0-2-0 TCH/F ESTABLISHED', |
| 204 | '1-0-3-0 TCH/F ESTABLISHED',), |
| 205 | ), |
| 206 | ) |
| 207 | |
| 208 | with test.report_fragment('08_call_stable'): |
| 209 | print('expect the call to continue for a while, to ensure the new lchan is functional') |
| 210 | for i in range(5): |
| 211 | sleep(5) |
| 212 | assert bsc.vty.active_lchans_match( |
| 213 | expected=('0-0-2-0 TCH/F ESTABLISHED', |
| 214 | '0-0-3-0 TCH/F ESTABLISHED',)) |
| 215 | print('call is still fine') |
| 216 | |
| 217 | assert ms0.call_is_active(mo_cid) and ms1.call_is_active(mt_cid) |
| 218 | ms1.call_hangup(mt_cid) |
| 219 | wait(lambda: len(ms0.call_id_list()) == 0 and len(ms1.call_id_list()) == 0) |
| 220 | print('hangup success') |
| 221 | |
| 222 | # vim: tabstop=4 shiftwidth=4 expandtab |