diff --git a/src/cli/graph.py b/src/cli/graph.py index 35d15558a..df0b2f560 100644 --- a/src/cli/graph.py +++ b/src/cli/graph.py @@ -12,23 +12,23 @@ def graph(): @graph.command() -@click.argument("params", nargs=-1, type=str) +@click.argument("number", type=int) @click.option("--outfile", type=Path) @click.option("--version", type=str, default=DEFAULT_TAG) @click.option("--bitcoin_conf", type=Path) @click.option("--random", is_flag=True) def create( - params: list[str], outfile: Path, version: str, bitcoin_conf: Path, random: bool = False + number: int, outfile: Path, version: str, bitcoin_conf: Path, random: bool = False ): """ - Create a graph file of type random AS graph with [params] + Create a cycle graph with [n] nodes, and additionally include 7 extra random outbounds per node. Returns XML file as string with or without --outfile option """ print( rpc_call( "graph_generate", { - "params": params, + "n": number, "outfile": str(outfile) if outfile else "", "version": version, "bitcoin_conf": str(bitcoin_conf), diff --git a/src/warnet/server.py b/src/warnet/server.py index 8a9dad805..e3e547e01 100644 --- a/src/warnet/server.py +++ b/src/warnet/server.py @@ -21,7 +21,7 @@ from flask_jsonrpc.app import JSONRPC from flask_jsonrpc.exceptions import ServerError from warnet.utils import ( - create_graph_with_probability, + create_cycle_graph, gen_config_dir, ) from warnet.warnet import Warnet @@ -412,26 +412,21 @@ def thread_start(wn, lock: threading.Lock): def graph_generate( self, - params: list[str], + n: int, outfile: str, version: str, bitcoin_conf: str | None = None, random: bool = False, ) -> str: try: - graph_func = nx.generators.erdos_renyi_graph - # Default connectivity probability of 0.2 - if not any(param.startswith("p=") for param in params): - params.append("p=0.2") - - graph = create_graph_with_probability(graph_func, params, version, bitcoin_conf, random) + graph = create_cycle_graph(n, version, bitcoin_conf, random) if outfile: file_path = Path(outfile) - nx.write_graphml(graph, file_path) + nx.write_graphml(graph, file_path, named_key_ids=True) return f"Generated graph written to file: {outfile}" bio = BytesIO() - nx.write_graphml(graph, bio) + nx.write_graphml(graph, bio, named_key_ids=True) xml_data = bio.getvalue() return xml_data.decode('utf-8') except Exception as e: diff --git a/src/warnet/utils.py b/src/warnet/utils.py index 11810c8a6..2a1b42c21 100644 --- a/src/warnet/utils.py +++ b/src/warnet/utils.py @@ -421,54 +421,31 @@ def default_bitcoin_conf_args() -> str: return " ".join(conf_args) -def create_graph_with_probability( - graph_func, params: list, version: str, bitcoin_conf: str | None, random_version: bool +def create_cycle_graph( + n: int, version: str, bitcoin_conf: str | None, random_version: bool ): - kwargs = {} - for param in params: - try: - key, value = param.split("=") - kwargs[key] = value - except ValueError: - msg = f"Invalid parameter format: {param}" - logger.error(msg) - return msg - - # Attempt to convert numerical values from string to their respective numerical types - msg = "Error convering numerical value strings to types " - for key in kwargs: - try: - kwargs[key] = int(kwargs[key]) - except ValueError: - try: - kwargs[key] = float(kwargs[key]) - except ValueError as e: - msg += str(e) - return msg - except Exception as e: - msg += str(e) - return msg - - logger.debug(f"Parsed params: {kwargs}") try: - graph = graph_func(**kwargs) + # Use nx.DiGraph() as base otherwise edges not always made in specific directions + graph = nx.generators.cycle_graph(n, nx.DiGraph()) except TypeError as e: msg = f"Failed to create graph: {e}" logger.error(msg) return msg - # Ensure each node has at least 8 edges + # Graph is a simply cycle graph with all nodes connected in a loop, including both ends. + # Ensure each node has at least 8 outbound connections by making 7 more outbound connections for node in graph.nodes(): - while graph.degree(node) < 8: + logger.debug(f"Creating additional connections for node {node}") + for _ in range(8): # Choose a random node to connect to # Make sure it's not the same node and they aren't already connected - potential_nodes = [ - n for n in range(kwargs["n"]) if n != node and not graph.has_edge(node, n) - ] + potential_nodes = [ node for node in range(n) if n != node and not graph.has_edge(node, n) ] if potential_nodes: chosen_node = random.choice(potential_nodes) graph.add_edge(node, chosen_node) + logger.debug(f"Added edge: {node}:{chosen_node}") + logger.debug(f"Node {node} edges: {graph.edges(node)}") # calculate degree degree_dict = dict(graph.degree(graph.nodes())) @@ -491,13 +468,20 @@ def create_graph_with_probability( conf_contents = dump_bitcoin_conf(conf_dict, for_graph=True) # populate our custom fields - for node in graph.nodes(): + for i, node in enumerate(graph.nodes()): if random_version: graph.nodes[node]["version"] = random.choice(WEIGHTED_TAGS) else: - graph.nodes[node]["version"] = version + # One node demoing the image tag + if i == 1: + graph.nodes[node]["image"] = f"bitcoindevproject/bitcoin:{version}" + else: + graph.nodes[node]["version"] = version graph.nodes[node]["bitcoin_config"] = conf_contents graph.nodes[node]["tc_netem"] = "" + graph.nodes[node]["build_args"] = "" + graph.nodes[node]["exporter"] = "" + graph.nodes[node]["collect_logs"] = "" convert_unsupported_attributes(graph) return graph diff --git a/test/graph_test.py b/test/graph_test.py index 777e0842e..02ab58409 100755 --- a/test/graph_test.py +++ b/test/graph_test.py @@ -14,11 +14,11 @@ tf = f"{dir}/{str(uuid.uuid4())}.graphml" if base.backend == "compose": print(f"Server writing test graph directly to {tf}") - print(base.warcli(f"graph create n=10 --outfile={tf} --version={DEFAULT_TAG}", network=False)) + print(base.warcli(f"graph create 10 --outfile={tf} --version={DEFAULT_TAG}", network=False)) base.wait_for_predicate(lambda: Path(tf).exists()) else: print(f"Client retrieving test graph from RPC and writing to {tf}") - xml = base.warcli(f"graph create n=10 --version={DEFAULT_TAG}", network=False) + xml = base.warcli(f"graph create 10 --version={DEFAULT_TAG}", network=False) print(xml) with open(tf, "w") as file: file.write(xml)