Spaces:
Running
Running
Update ipmentor/tools.py
Browse files- ipmentor/tools.py +117 -37
ipmentor/tools.py
CHANGED
|
@@ -542,24 +542,103 @@ def generate_diagram(ip_network: str, hosts_list: str, use_svg: bool = False) ->
|
|
| 542 |
return json.dumps({"error": str(e)}, indent=2)
|
| 543 |
|
| 544 |
|
| 545 |
-
def
|
|
|
|
| 546 |
"""
|
| 547 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
|
| 549 |
Args:
|
| 550 |
-
num_subnets (int): Number of subnets required
|
| 551 |
use_vlsm (bool): If True, each subnet has different host requirements (VLSM)
|
| 552 |
If False, equal division of subnets
|
| 553 |
|
| 554 |
Returns:
|
| 555 |
-
str:
|
|
|
|
|
|
|
|
|
|
| 556 |
"""
|
| 557 |
try:
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
if
|
| 562 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
|
| 564 |
# RFC 1918 Private Address Spaces
|
| 565 |
private_ranges = [
|
|
@@ -654,16 +733,16 @@ def generate_subnetting_exercise(num_subnets: int, use_vlsm: bool = False) -> st
|
|
| 654 |
validation = calculate_subnets(network_str, num_subnets, "vlsm", hosts_list)
|
| 655 |
|
| 656 |
if "error" not in validation:
|
| 657 |
-
# Success!
|
| 658 |
-
return
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
"
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
|
| 668 |
# If we couldn't find valid hosts with random generation, try fallback
|
| 669 |
# Only try fallback for /18 or larger (at least 16384 addresses)
|
|
@@ -678,30 +757,31 @@ def generate_subnetting_exercise(num_subnets: int, use_vlsm: bool = False) -> st
|
|
| 678 |
|
| 679 |
validation = calculate_subnets(network_str, num_subnets, "vlsm", hosts_list)
|
| 680 |
if "error" not in validation:
|
| 681 |
-
return
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
"
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
|
| 691 |
else:
|
| 692 |
# Equal division - validate directly
|
| 693 |
validation = calculate_subnets(network_str, num_subnets, "max_subnets", "")
|
| 694 |
|
| 695 |
if "error" not in validation:
|
| 696 |
-
# Success!
|
| 697 |
-
return
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
"
|
| 703 |
-
|
| 704 |
-
|
|
|
|
| 705 |
|
| 706 |
# If we exhausted all network sizes, return error
|
| 707 |
return json.dumps({
|
|
|
|
| 542 |
return json.dumps({"error": str(e)}, indent=2)
|
| 543 |
|
| 544 |
|
| 545 |
+
def _build_complete_exercise(network: str, mask: str, mask_decimal: str, num_subnets: int,
|
| 546 |
+
exercise_type: str, hosts_per_subnet, hosts_list: str = "") -> str:
|
| 547 |
"""
|
| 548 |
+
Build a complete exercise with solution and diagram.
|
| 549 |
+
|
| 550 |
+
Args:
|
| 551 |
+
network: Network address
|
| 552 |
+
mask: CIDR mask (e.g., "/24")
|
| 553 |
+
mask_decimal: Decimal mask (e.g., "255.255.255.0")
|
| 554 |
+
num_subnets: Number of subnets
|
| 555 |
+
exercise_type: "VLSM" or "Equal Division"
|
| 556 |
+
hosts_per_subnet: List of host counts (VLSM) or single count (Equal)
|
| 557 |
+
hosts_list: Comma-separated string of hosts (for VLSM)
|
| 558 |
+
|
| 559 |
+
Returns:
|
| 560 |
+
JSON string with exercise, solution, and diagram
|
| 561 |
+
"""
|
| 562 |
+
try:
|
| 563 |
+
network_str = f"{network}{mask}"
|
| 564 |
+
|
| 565 |
+
# Build exercise object
|
| 566 |
+
exercise = {
|
| 567 |
+
"network": network,
|
| 568 |
+
"mask": mask,
|
| 569 |
+
"mask_decimal": mask_decimal,
|
| 570 |
+
"num_subnets": num_subnets,
|
| 571 |
+
"type": exercise_type
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
if exercise_type == "VLSM":
|
| 575 |
+
exercise["hosts_per_subnet"] = hosts_per_subnet
|
| 576 |
+
exercise["hosts_list"] = hosts_list
|
| 577 |
+
else:
|
| 578 |
+
exercise["hosts_per_subnet"] = hosts_per_subnet
|
| 579 |
+
|
| 580 |
+
# Calculate solution
|
| 581 |
+
if exercise_type == "VLSM":
|
| 582 |
+
solution = calculate_subnets(network_str, num_subnets, "vlsm", hosts_list)
|
| 583 |
+
else:
|
| 584 |
+
solution = calculate_subnets(network_str, num_subnets, "max_subnets", "")
|
| 585 |
+
|
| 586 |
+
# Generate diagram (optional - may fail if d2 not installed)
|
| 587 |
+
diagram_path = None
|
| 588 |
+
try:
|
| 589 |
+
if exercise_type == "VLSM":
|
| 590 |
+
diagram_result_json = generate_diagram(network_str, hosts_list, False)
|
| 591 |
+
else:
|
| 592 |
+
# For equal division, create hosts list from solution
|
| 593 |
+
hosts_count = solution.get("hosts_per_subnet", 0)
|
| 594 |
+
hosts_list_for_diagram = ",".join([str(hosts_count)] * num_subnets)
|
| 595 |
+
diagram_result_json = generate_diagram(network_str, hosts_list_for_diagram, False)
|
| 596 |
+
|
| 597 |
+
# Parse diagram result (it's a JSON string)
|
| 598 |
+
diagram_result = json.loads(diagram_result_json)
|
| 599 |
+
if diagram_result.get("success"):
|
| 600 |
+
diagram_path = diagram_result.get("image_path")
|
| 601 |
+
except Exception:
|
| 602 |
+
# Diagram generation failed (e.g., d2 not installed) - continue without it
|
| 603 |
+
pass
|
| 604 |
+
|
| 605 |
+
# Build complete result
|
| 606 |
+
result = {
|
| 607 |
+
"exercise": exercise,
|
| 608 |
+
"solution": solution,
|
| 609 |
+
"diagram_path": diagram_path
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
return json.dumps(result, indent=2)
|
| 613 |
+
|
| 614 |
+
except Exception as e:
|
| 615 |
+
return json.dumps({"error": f"Failed to build complete exercise: {str(e)}"}, indent=2)
|
| 616 |
+
|
| 617 |
+
|
| 618 |
+
def generate_subnetting_exercise(use_vlsm: bool = False) -> str:
|
| 619 |
+
"""
|
| 620 |
+
Generate a complete random subnetting exercise with solution and diagram.
|
| 621 |
|
| 622 |
Args:
|
|
|
|
| 623 |
use_vlsm (bool): If True, each subnet has different host requirements (VLSM)
|
| 624 |
If False, equal division of subnets
|
| 625 |
|
| 626 |
Returns:
|
| 627 |
+
str: Complete exercise in JSON format including:
|
| 628 |
+
- exercise: network, mask, num_subnets, hosts requirements
|
| 629 |
+
- solution: calculated subnets with all details
|
| 630 |
+
- diagram_path: path to generated network diagram
|
| 631 |
"""
|
| 632 |
try:
|
| 633 |
+
# Generate random number of subnets with weighted distribution
|
| 634 |
+
# 60% easy (2-8), 30% medium (9-16), 10% hard (17-32)
|
| 635 |
+
rand = random.random()
|
| 636 |
+
if rand < 0.6: # 60% probability
|
| 637 |
+
num_subnets = random.randint(2, 8)
|
| 638 |
+
elif rand < 0.9: # 30% probability (0.6 + 0.3)
|
| 639 |
+
num_subnets = random.randint(9, 16)
|
| 640 |
+
else: # 10% probability
|
| 641 |
+
num_subnets = random.randint(17, 32)
|
| 642 |
|
| 643 |
# RFC 1918 Private Address Spaces
|
| 644 |
private_ranges = [
|
|
|
|
| 733 |
validation = calculate_subnets(network_str, num_subnets, "vlsm", hosts_list)
|
| 734 |
|
| 735 |
if "error" not in validation:
|
| 736 |
+
# Success! Build complete exercise with solution and diagram
|
| 737 |
+
return _build_complete_exercise(
|
| 738 |
+
str(selected_network.network_address),
|
| 739 |
+
f"/{network_cidr}",
|
| 740 |
+
str(selected_network.netmask),
|
| 741 |
+
num_subnets,
|
| 742 |
+
"VLSM",
|
| 743 |
+
host_sizes,
|
| 744 |
+
hosts_list
|
| 745 |
+
)
|
| 746 |
|
| 747 |
# If we couldn't find valid hosts with random generation, try fallback
|
| 748 |
# Only try fallback for /18 or larger (at least 16384 addresses)
|
|
|
|
| 757 |
|
| 758 |
validation = calculate_subnets(network_str, num_subnets, "vlsm", hosts_list)
|
| 759 |
if "error" not in validation:
|
| 760 |
+
return _build_complete_exercise(
|
| 761 |
+
str(selected_network.network_address),
|
| 762 |
+
f"/{network_cidr}",
|
| 763 |
+
str(selected_network.netmask),
|
| 764 |
+
num_subnets,
|
| 765 |
+
"VLSM",
|
| 766 |
+
host_sizes,
|
| 767 |
+
hosts_list
|
| 768 |
+
)
|
| 769 |
|
| 770 |
else:
|
| 771 |
# Equal division - validate directly
|
| 772 |
validation = calculate_subnets(network_str, num_subnets, "max_subnets", "")
|
| 773 |
|
| 774 |
if "error" not in validation:
|
| 775 |
+
# Success! Build complete exercise with solution and diagram
|
| 776 |
+
return _build_complete_exercise(
|
| 777 |
+
str(selected_network.network_address),
|
| 778 |
+
f"/{network_cidr}",
|
| 779 |
+
str(selected_network.netmask),
|
| 780 |
+
num_subnets,
|
| 781 |
+
"Equal Division",
|
| 782 |
+
validation["hosts_per_subnet"],
|
| 783 |
+
""
|
| 784 |
+
)
|
| 785 |
|
| 786 |
# If we exhausted all network sizes, return error
|
| 787 |
return json.dumps({
|