зеркало из https://github.com/microsoft/CCF.git
124 строки
4.3 KiB
Python
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
|