davidlms commited on
Commit
decfedf
·
verified ·
1 Parent(s): 6c78ba8

Update ipmentor/tools.py

Browse files
Files changed (1) hide show
  1. 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 generate_subnetting_exercise(num_subnets: int, use_vlsm: bool = False) -> str:
 
546
  """
547
- Generate a random subnetting exercise that is solvable.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: Exercise specification in JSON format
 
 
 
556
  """
557
  try:
558
- if num_subnets < 1:
559
- return json.dumps({"error": "Number of subnets must be at least 1"}, indent=2)
560
-
561
- if num_subnets > 256:
562
- return json.dumps({"error": "Number of subnets too large (max 256)"}, indent=2)
 
 
 
 
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! Return the exercise
658
- return json.dumps({
659
- "network": str(selected_network.network_address),
660
- "mask": f"/{network_cidr}",
661
- "mask_decimal": str(selected_network.netmask),
662
- "num_subnets": num_subnets,
663
- "type": "VLSM",
664
- "hosts_per_subnet": host_sizes,
665
- "hosts_list": hosts_list
666
- }, indent=2)
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 json.dumps({
682
- "network": str(selected_network.network_address),
683
- "mask": f"/{network_cidr}",
684
- "mask_decimal": str(selected_network.netmask),
685
- "num_subnets": num_subnets,
686
- "type": "VLSM",
687
- "hosts_per_subnet": host_sizes,
688
- "hosts_list": hosts_list
689
- }, indent=2)
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! Return the exercise
697
- return json.dumps({
698
- "network": str(selected_network.network_address),
699
- "mask": f"/{network_cidr}",
700
- "mask_decimal": str(selected_network.netmask),
701
- "num_subnets": num_subnets,
702
- "type": "Equal Division",
703
- "hosts_per_subnet": validation["hosts_per_subnet"]
704
- }, indent=2)
 
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({