CCF/tests/tvc.py

124 строки
4.3 KiB
Python

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the Apache 2.0 License.
import httpx
import random
import json
import argparse
"""
1. Run sandbox
~/CCF/build$ ../tests/sandbox/sandbox.sh --js-app-bundle ../samples/apps/basic_tv/js/ -n local://127.0.0.1:8000 -n local://127.0.0.1:8001
...
2. Run tvc.py
~/CCF/tests$ python3 tvc.py -t https://127.0.0.1:8000 -t https://127.0.0.1:8001 --ca ../build/workspace/sandbox_common/service_cert.pem
3. Trace is printed to stdout
{"action": "RwTxRequestAction", "type": "RwTxRequest", "tx": 0}
{"action": "RwTxResponseAction", "type": "RwTxResponse", "tx": 0, "tx_id": [2, 197]}
{"action": "StatusCommittedResponseAction", "type": "TxStatusReceived", "tx_id": [2, 197], "status": "CommittedStatus"}
{"action": "RwTxRequestAction", "type": "RwTxRequest", "tx": 1}
{"action": "RwTxResponseAction", "type": "RwTxResponse", "tx": 1, "tx_id": [2, 199]}
{"action": "StatusCommittedResponseAction", "type": "TxStatusReceived", "tx_id": [2, 199], "status": "CommittedStatus"}
{"action": "RwTxRequestAction", "type": "RwTxRequest", "tx": 2}
{"action": "RwTxResponseAction", "type": "RwTxResponse", "tx": 2, "tx_id": [2, 201]}
"""
KEY = "0"
VALUE = "value"
def log(**kwargs):
print(json.dumps(kwargs))
def tx_id(string):
view, seqno = string.split(".")
return int(view), int(seqno)
def retry(call, urls, **kwargs):
"""
Retry http calls if they time out (process suspended during execution),
or return a non-200/204 code (unable to forward because primary unknown).
Pick a random URL, to avoid getting stuck too long on a suspended node.
"""
response = None
while response is None or response.status_code not in (200, 204):
try:
url = random.choice(urls)
response = call(url, **kwargs)
except (httpx.ReadTimeout, httpx.ConnectTimeout):
pass
return response
def run(targets, cacert):
session = httpx.Client(verify=cacert)
tx = -1
key_urls = [f"{target}/records/{KEY}" for target in targets]
while True:
tx += 1
# Always start with a write, to avoid having to handle missing values
txtype = random.choice(["Ro", "Rw"]) if tx else "Rw"
if txtype == "Ro":
response = retry(session.get, key_urls)
log(action=f"{txtype}TxRequestAction", type=f"{txtype}TxRequest", tx=tx)
assert response.text == VALUE
txid = response.headers["x-ms-ccf-transaction-id"]
log(
action="RoTxResponseAction",
type="RoTxResponse",
tx=tx,
tx_id=tx_id(txid),
)
elif txtype == "Rw":
response = retry(session.put, key_urls, data=VALUE)
log(action=f"{txtype}TxRequestAction", type=f"{txtype}TxRequest", tx=tx)
txid = response.headers["x-ms-ccf-transaction-id"]
log(
action="RwTxExecuteAction",
type="RwTxExecute",
tx_id=tx_id(txid),
tx=tx,
)
log(
action="RwTxResponseAction",
type="RwTxResponse",
tx=tx,
tx_id=tx_id(txid),
)
done = False
while not done:
tx_urls = [f"{target}/tx?transaction_id={txid}" for target in targets]
response = retry(session.get, tx_urls)
status = response.json()["status"]
if status in ("Committed", "Invalid"):
log(
action=f"Status{status}ResponseAction",
type="TxStatusReceived",
tx_id=tx_id(txid),
status=f"{status}Status",
)
done = True
else:
raise ValueError(f"Unknown Tx type: {txtype}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Run --txs steps, a ~50% mix of reads and writes, randomly distributed across --target nodes"
)
parser.add_argument("-t", "--target", help="Host to connect to", action="append")
parser.add_argument("--ca", help="CA for the server")
args = parser.parse_args()
try:
run(args.target, args.ca)
except KeyboardInterrupt:
pass