|
13 | 13 | import signal |
14 | 14 | import jwt |
15 | 15 | import tempfile |
| 16 | +from concurrent.futures import ThreadPoolExecutor, as_completed |
16 | 17 | from json import dumps as json_dumps |
17 | 18 | from json import load as json_load |
18 | 19 | from json import loads as json_loads |
|
22 | 23 | from subprocess import CalledProcessError |
23 | 24 | from sys import exit as sysexit |
24 | 25 | from sys import stderr, stdin, stdout |
| 26 | +from threading import Lock |
25 | 27 | from xml.sax.xmlreader import InputSource |
26 | 28 |
|
27 | 29 | from jwt.exceptions import PyJWTError |
@@ -961,32 +963,55 @@ def demo(cli): |
961 | 963 | else: |
962 | 964 | spinner.succeed(f"Found a hosted router in {region}") |
963 | 965 |
|
964 | | - spinner.text = f"Creating {len(fabric_placements)} hosted router(s)" |
965 | | - with spinner: |
966 | | - for region in fabric_placements: |
967 | | - er_name = f"Hosted Router {region} [{cli.config.demo.provider}]" |
968 | | - if not network.edge_router_exists(er_name): |
969 | | - er = network.create_edge_router( |
970 | | - name=er_name, |
971 | | - attributes=[ |
972 | | - "#hosted_routers", |
973 | | - "#demo_exits", |
974 | | - f"#{cli.config.demo.provider}", |
975 | | - ], |
976 | | - provider=cli.config.demo.provider, |
977 | | - location_code=region, |
978 | | - tunneler_enabled=False, # workaround for MOP-18098 (missing tunneler binding in ziti-router config) |
979 | | - ) |
980 | | - hosted_edge_routers.extend([er]) |
981 | | - spinner.succeed(f"Created {cli.config.demo.provider} router in {region}") |
| 966 | + # Helper function to create or validate a single router (runs in parallel) |
| 967 | + def create_or_validate_router(region): |
| 968 | + """Create or validate router for a region. Returns (region, router_dict, message).""" |
| 969 | + er_name = f"Hosted Router {region} [{cli.config.demo.provider}]" |
| 970 | + if not network.edge_router_exists(er_name): |
| 971 | + er = network.create_edge_router( |
| 972 | + name=er_name, |
| 973 | + attributes=[ |
| 974 | + "#hosted_routers", |
| 975 | + "#demo_exits", |
| 976 | + f"#{cli.config.demo.provider}", |
| 977 | + ], |
| 978 | + provider=cli.config.demo.provider, |
| 979 | + location_code=region, |
| 980 | + tunneler_enabled=False, # workaround for MOP-18098 (missing tunneler binding in ziti-router config) |
| 981 | + ) |
| 982 | + message = f"Created {cli.config.demo.provider} router in {region}" |
| 983 | + return (region, er, message) |
| 984 | + else: |
| 985 | + er_matches = network.edge_routers(name=er_name, only_hosted=True) |
| 986 | + if len(er_matches) == 1: |
| 987 | + er = er_matches[0] |
982 | 988 | else: |
983 | | - er_matches = network.edge_routers(name=er_name, only_hosted=True) |
984 | | - if len(er_matches) == 1: |
985 | | - er = er_matches[0] |
986 | | - else: |
987 | | - raise RuntimeError(f"unexpectedly found more than one matching router for name '{er_name}'") |
988 | | - if er['status'] in RESOURCES["edge-routers"].status_symbols["error"] + RESOURCES["edge-routers"].status_symbols["deleting"] + RESOURCES["edge-routers"].status_symbols["deleted"]: |
989 | | - raise RuntimeError(f"hosted router '{er_name}' has unexpected status '{er['status']}'") |
| 989 | + raise RuntimeError(f"unexpectedly found more than one matching router for name '{er_name}'") |
| 990 | + if er['status'] in RESOURCES["edge-routers"].status_symbols["error"] + RESOURCES["edge-routers"].status_symbols["deleting"] + RESOURCES["edge-routers"].status_symbols["deleted"]: |
| 991 | + raise RuntimeError(f"hosted router '{er_name}' has unexpected status '{er['status']}'") |
| 992 | + return (region, er, None) # No message for existing routers |
| 993 | + |
| 994 | + # Parallelize router creation with thread-safe spinner updates |
| 995 | + spinner.text = f"Creating {len(fabric_placements)} hosted router(s)" |
| 996 | + spinner_lock = Lock() |
| 997 | + new_routers = [] |
| 998 | + |
| 999 | + with ThreadPoolExecutor(max_workers=min(len(fabric_placements), 5)) as executor: |
| 1000 | + # Submit all router creation tasks |
| 1001 | + future_to_region = {executor.submit(create_or_validate_router, region): region for region in fabric_placements} |
| 1002 | + |
| 1003 | + # Collect results as they complete |
| 1004 | + for future in as_completed(future_to_region): |
| 1005 | + region, er, message = future.result() |
| 1006 | + new_routers.append(er) |
| 1007 | + |
| 1008 | + # Thread-safe spinner update for newly created routers |
| 1009 | + if message: |
| 1010 | + with spinner_lock: |
| 1011 | + spinner.succeed(message) |
| 1012 | + |
| 1013 | + # Add all new routers to the list |
| 1014 | + hosted_edge_routers.extend(new_routers) |
990 | 1015 |
|
991 | 1016 | if not len(hosted_edge_routers) > 0: |
992 | 1017 | raise RuntimeError("unexpected problem with router placements, found zero hosted routers") |
|
0 commit comments