Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Change the link while the test runs

Sometimes the moment of a change is the test’s: “when the app has connected, make the link worse”. A scenario file fixes its changes in advance (see Changes over time); a test can also change a link while the run goes.

The link has to be declared live when the run starts. The queues of a direction are built from every state it goes through and are not rebuilt later, so the run needs to know which links the code may change and builds those so that any state fits. A link that is not declared live is refused.

# file: stream.yaml
version: 0
name: live-stream
duration: 4s
seed: 7
topology:
  nodes: [client, server]
  links:
    - id: access
      between: [client, server]
      forward: { delay: 30ms, rate: 4mbit }
workloads:
  - { id: media, kind: udp-cbr, from: client, to: server, rate: 1mbit, packet-size: 1000b }
import netscenario as ns

with ns.start("stream.yaml", live=["access"], packets=True) as run:
    run.wait_until(1.5)                      # seconds into the measured phase
    steps = run.link("access").set(
        forward={"delay": "120ms", "loss": {"model": "random", "p": "20%"}}
    )
result = run.result()

(step,) = steps
print("the packet that met the old state:", step.packet)
assert step.live and step.packet is not None
assert result["media"].delay.max > 0.120

start returns at once with a Run. wait_until waits for a moment of the measured phase, run.link(...).set(...) changes a link (the keys are those of a set in the file), and down() and up() take it away and give it back. Leaving the with waits for the end of the run, and run.result() gives the result.

What the result records

A change from code is a step of the timeline, made between two packets: the sender is held before its next packet, the step is done, and the sender goes on. The step has live=True and packet, the number of the first packet that met the new state, so the result says exactly where the change landed.

The moment is the test’s, so two live runs differ, and the result says so (seed.not_reproducible_because). What it records is the scenario with the change as an event at the slot of that packet, and a replay of that directory does the change at the same packet and loses the same packets:

replay = ns.replay(result.out_dir)
assert replay.reproduced

With asyncio

The same run handle works from async code and never blocks the loop. Cancelling the task cancels the run and waits until the network is torn down, so no thread is left behind:

import asyncio

import netscenario as ns


async def main() -> None:
    async with await ns.start_async("stream.yaml", live=["access"]) as run:
        await run.wait_until(1.0)
        await run.link("access").down()
        await asyncio.sleep(0.5)
        await run.link("access").up()
    result = await run.result()
    print("lost:", result["media"].lost)


asyncio.run(main())

run.events() is an async iterator over the events of the run (when the network is built, when the workloads start, …). run_async(...) is the one-call form for a scenario that is only to be awaited. A change that the backend cannot do exactly (a state its queues do not fit) is refused to the caller as an error, before anything is touched, and the run goes on.