2019-04-26 18:27:27 +03:00
|
|
|
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
# Licensed under the Apache 2.0 License.
|
|
|
|
import os
|
|
|
|
import getpass
|
|
|
|
import logging
|
|
|
|
import time
|
|
|
|
import math
|
|
|
|
import infra.ccf
|
|
|
|
import infra.proc
|
|
|
|
import e2e_args
|
|
|
|
|
|
|
|
from loguru import logger as LOG
|
|
|
|
|
|
|
|
# This test starts from a given number of nodes (hosts), commits
|
2019-08-15 19:52:43 +03:00
|
|
|
# a transaction, stops the current primary, waits for an election and repeats
|
|
|
|
# this process until no progress can be made (i.e. no primary can be elected
|
2019-04-26 18:27:27 +03:00
|
|
|
# as F > N/2).
|
|
|
|
|
|
|
|
|
|
|
|
def wait_for_index_globally_committed(index, term, nodes):
|
|
|
|
"""
|
|
|
|
Wait for a specific version at a specific term to be committed on all nodes.
|
|
|
|
"""
|
|
|
|
for _ in range(infra.ccf.Network.replication_delay):
|
|
|
|
up_to_date_f = []
|
|
|
|
for f in nodes:
|
2019-10-01 18:17:14 +03:00
|
|
|
with f.node_client() as c:
|
Json schema: Take 3 (#113)
* Add a getApi method, listing all installed RPC method names
* Sketch RecordParams struct
* WIP
* Broken WIP
* Partial macro
* Basic examples working
* Partial file separation
* Move, rename, and fix FOR macro
* Use json get
* Build to_json from RequiredJsonFields too
* Remove unneeded pair specialisation
* Add comments, collide required and optional
* REformat
* Use new macros everywhere
* Remove unused template
* Rename getApi to listMethods
* Move frontend-specific calltypes to /rpc
* Specify GetTxHist return type
* Pretty-print client responses by default
* Add a GetSchema RPC
* Other tools demand ugly formatting by default
* mins and maxes for numerics, map of schemas
* Support _FOR_JSON_0
* Fix support for std::optional optional fields
* Test std optionals
* Define schemas for GetCommit
* More definitions for existing RPCs
* Tidy schema generation, including for vectors
* Add proper unit test
* Initial test of schema generation
* Fix failing tests
* Formatting
* Add (currently failing) test of nested structs
* Add misleadingly passing test
* Set correct expected pointers, test currently fails
* Oops - deexpand
* Correctly build pointer path for erroneous array elements
* Demonstrate invalid, not just missing, valeus
* Skeleton of json_bench
* Fix typo
* WIP
* Compare manual json parsers vs macro-defined
* mumble mumble
* Add valijson, +basic test
* Add benchmark of valijson validation
* Benchmark simple and complex structs
* Additional broken schema test
* Include pointer to parse errors
* Restore old basic translator macro
* Restore simpler macro for translators that don't need schema
* Add auto schema for private logging methods
* Add manual schema + validation for PUBLIC logging RPCs
* Match RPC format
* More RPC format fixes
* Correct scenario test target
* Add documentation entry on API schema
* Initial schema retrieval test
* Correct URLs in generated schema
* Send schema to a flat folder
* Remove unnecessary size_t max restriction
* Report non-matching schema
* Add current schemas
* Tidying
* clang-format
* Remove schema generation e2e test
* fmtlib, remove $id from schema
* Fix pointer paths
2019-06-05 12:36:50 +03:00
|
|
|
id = c.request("getCommit", {"commit": index})
|
2019-04-26 18:27:27 +03:00
|
|
|
res = c.response(id)
|
2019-11-25 19:52:04 +03:00
|
|
|
if res.result["term"] == term and (res.global_commit >= index):
|
2019-04-26 18:27:27 +03:00
|
|
|
up_to_date_f.append(f.node_id)
|
|
|
|
if len(up_to_date_f) == len(nodes):
|
|
|
|
break
|
|
|
|
time.sleep(1)
|
|
|
|
assert len(up_to_date_f) == len(
|
|
|
|
nodes
|
2019-08-15 19:52:43 +03:00
|
|
|
), "Only {} out of {} backups are up to date".format(len(up_to_date_f), len(nodes))
|
2019-04-26 18:27:27 +03:00
|
|
|
|
|
|
|
|
|
|
|
def run(args):
|
|
|
|
# Three nodes minimum to make sure that the raft network can still make progress
|
|
|
|
# if one node stops
|
2019-11-25 19:52:04 +03:00
|
|
|
|
|
|
|
if args.consensus == "pbft":
|
|
|
|
hosts = ["localhost", "localhost", "localhost", "localhost"]
|
|
|
|
else:
|
|
|
|
hosts = ["localhost", "localhost", "localhost"]
|
2019-04-26 18:27:27 +03:00
|
|
|
|
|
|
|
with infra.ccf.network(
|
|
|
|
hosts, args.build_dir, args.debug_nodes, args.perf_nodes, pdb=args.pdb
|
|
|
|
) as network:
|
|
|
|
|
2019-11-08 12:33:47 +03:00
|
|
|
network.start_and_join(args)
|
2019-04-26 18:27:27 +03:00
|
|
|
current_term = None
|
|
|
|
|
|
|
|
# Time before an election completes
|
|
|
|
max_election_duration = args.election_timeout * 4 // 1000
|
|
|
|
|
|
|
|
# Number of nodes F to stop until network cannot make progress
|
|
|
|
nodes_to_stop = math.ceil(len(hosts) / 2)
|
2019-11-25 19:52:04 +03:00
|
|
|
if args.consensus == "pbft":
|
|
|
|
nodes_to_stop = math.ceil(len(hosts) / 3)
|
2019-04-26 18:27:27 +03:00
|
|
|
|
|
|
|
for _ in range(nodes_to_stop):
|
2019-08-15 19:52:43 +03:00
|
|
|
# Note that for the first iteration, the primary is known in advance anyway
|
|
|
|
LOG.debug("Find freshly elected primary")
|
|
|
|
primary, current_term = network.find_primary()
|
2019-04-26 18:27:27 +03:00
|
|
|
|
2019-11-25 19:52:04 +03:00
|
|
|
LOG.debug(
|
|
|
|
"Commit new transactions, primary:{}, current_term:{}".format(
|
|
|
|
primary, current_term
|
|
|
|
)
|
|
|
|
)
|
2019-04-26 18:27:27 +03:00
|
|
|
commit_index = None
|
2019-11-13 12:54:32 +03:00
|
|
|
with primary.user_client(format="json") as c:
|
2019-04-26 18:27:27 +03:00
|
|
|
res = c.do(
|
|
|
|
"LOG_record",
|
|
|
|
{
|
|
|
|
"id": current_term,
|
|
|
|
"msg": "This log is committed in term {}".format(current_term),
|
|
|
|
},
|
2019-10-07 17:18:10 +03:00
|
|
|
readonly_hint=None,
|
|
|
|
expected_result=True,
|
2019-04-26 18:27:27 +03:00
|
|
|
)
|
|
|
|
commit_index = res.commit
|
|
|
|
|
|
|
|
LOG.debug("Waiting for transaction to be committed by all nodes")
|
|
|
|
wait_for_index_globally_committed(
|
2019-10-01 19:07:29 +03:00
|
|
|
commit_index, current_term, network.get_joined_nodes()
|
2019-04-26 18:27:27 +03:00
|
|
|
)
|
|
|
|
|
2019-08-15 19:52:43 +03:00
|
|
|
LOG.debug("Stopping primary")
|
|
|
|
primary.stop()
|
2019-04-26 18:27:27 +03:00
|
|
|
|
2019-09-10 13:34:21 +03:00
|
|
|
LOG.debug("Waiting for a new primary to be elected...")
|
2019-04-26 18:27:27 +03:00
|
|
|
time.sleep(max_election_duration)
|
|
|
|
|
|
|
|
# More than F nodes have been stopped, trying to commit any message
|
|
|
|
LOG.debug(
|
|
|
|
"No progress can be made as more than {} nodes have stopped".format(
|
|
|
|
nodes_to_stop
|
|
|
|
)
|
|
|
|
)
|
2019-05-20 13:45:53 +03:00
|
|
|
try:
|
2019-08-15 19:52:43 +03:00
|
|
|
primary, current_term = network.find_primary()
|
|
|
|
assert False, "Primary should not be found"
|
2019-11-25 19:52:04 +03:00
|
|
|
except TypeError:
|
|
|
|
assert args.consensus == "pbft", "Unexpected error"
|
2019-05-20 13:45:53 +03:00
|
|
|
except AssertionError:
|
2019-11-25 19:52:04 +03:00
|
|
|
assert args.consensus == "raft", "Unexpected error"
|
|
|
|
|
|
|
|
LOG.info(
|
|
|
|
"As expected, primary could not be found after election timeout. Test ended successfully."
|
|
|
|
)
|
2019-04-26 18:27:27 +03:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
args = e2e_args.cli_args()
|
|
|
|
args.package = "libloggingenc"
|
|
|
|
run(args)
|