Skip to content

Add tools for static network configuration#91

Closed
keitwb wants to merge 1 commit intoopenshift-assisted:masterfrom
keitwb:static-hosts
Closed

Add tools for static network configuration#91
keitwb wants to merge 1 commit intoopenshift-assisted:masterfrom
keitwb:static-hosts

Conversation

@keitwb
Copy link
Copy Markdown
Contributor

@keitwb keitwb commented Sep 9, 2025

Right now it is pretty basic with just the ability to add basic network config with a predefined NMState template.

The API handling is a bit awkward given the MAC to IP mapping but all of that complexity is hidden from the LLM.

Summary by CodeRabbit

  • New Features

    • Add host-level static networking tools: set IP, DNS, subnet prefix, and gateway; remove a host's static network config; list infrastructure environments for a cluster.
    • Automatically generate NMState YAML for configured hosts.
  • Tests

    • Add comprehensive unit tests covering creation, update, removal, YAML generation, and error handling for static network configuration.

@openshift-ci openshift-ci Bot requested review from carbonin and omertuc September 9, 2025 17:24
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Sep 9, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: keitwb

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 9, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 9, 2025

Walkthrough

Adds static network configuration support: a new service_client/static_net.py module with helpers and tests, and three MCP tools in server.py to list infra-envs and add/update/delete per-host static network configs; imports and metrics decorators were adjusted.

Changes

Cohort / File(s) Summary
MCP tools & imports
server.py
Added MCP tools update_static_network_config_for_host, delete_static_network_config_for_host, list_infra_envs; integrated static-net helpers (add_or_update_static_host_config, remove_static_host_config); consolidated metrics/track_tool_usage imports and decorators.
Static network helper module
service_client/static_net.py
New module defining MacInterfaceMap and HostStaticNetworkConfig TypedDicts and functions: add_or_update_static_host_config, find_host_config_index_for_mac, generate_basic_nmstate_yaml, remove_static_host_config. Handles JSON parsing and NMState YAML generation.
Unit tests
tests/test_static_net.py
New tests covering index lookup, NMState YAML generation, add/update behaviors, removal behaviors, and error cases (invalid JSON, missing MAC, empty configs).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant Server as MCP Tool (server.py)
  participant SN as static_net helpers
  participant IC as InventoryClient / API

  rect rgba(230,245,255,0.6)
  note over Client,Server: Add or update host static network config
  Client->>Server: update_static_network_config_for_host(cluster_id,dns,mac,ip,prefix,gw)
  Server->>IC: _get_cluster_infra_env_id(cluster_id) / get_infra_env(id)
  IC-->>Server: infra_env (includes static_network_config)
  Server->>SN: add_or_update_static_host_config(existing_json, dns, mac, ip, prefix, gw)
  SN-->>Server: updated_configs
  Server->>IC: update_infra_env(id, static_network_config=updated_configs)
  IC-->>Server: result
  Server-->>Client: result (string)
  end

  rect rgba(240,255,240,0.6)
  note over Client,Server: Delete host static network config
  Client->>Server: delete_static_network_config_for_host(cluster_id, mac)
  Server->>IC: _get_cluster_infra_env_id(cluster_id) / get_infra_env(id)
  IC-->>Server: infra_env
  Server->>SN: remove_static_host_config(existing_json, mac)
  SN-->>Server: updated_configs
  Server->>IC: update_infra_env(id, static_network_config=updated_configs)
  IC-->>Server: result
  Server-->>Client: result (string)
  end

  rect rgba(255,250,230,0.6)
  note over Client,Server: List infra-envs for a cluster
  Client->>Server: list_infra_envs(cluster_id)
  Server->>IC: list_infra_envs(cluster_id)
  IC-->>Server: infra_envs[]
  Server-->>Client: JSON-encoded list
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly describes the primary change in this pull request by indicating that new tools are being added to handle static network configuration, which directly reflects the main additions in both the server and client modules without extraneous detail.
Description Check ✅ Passed The description clearly relates to the changes in this PR by noting the introduction of basic static network configuration capabilities via an NMState template and referencing the underlying MAC-to-IP mapping complexity, aligning with the added tooling in the diff.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Poem

I twitch my whiskers, parse the net,
YAML blooms where hosts are set.
With MACs in maps and gateways true,
I hop through infra, configs new.
Update, delete, list—so neat! 🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci openshift-ci Bot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Sep 9, 2025
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (5)
service_client/static_net.py (2)

78-106: Prefer structured YAML emission to avoid injection/format fragility.

F-strings embed raw user values; use a dict + yaml.safe_dump for robustness (optional if you don’t want a PyYAML dep).

+from textwrap import dedent
+# Optional:
+# import yaml
@@
-def generate_basic_nmstate_yaml(
+def generate_basic_nmstate_yaml(
     dns_server: str,
     ip_address: str,
     subnet_prefix_len: int,
     gateway_address: str,
 ) -> str:
     """Generate a basic NMState config with the given parameters."""
-    return f"""
+    return f"""
 interfaces:
   - name: eth0
@@
       table-id: 254
 """

Optional structured approach (outside selected range for context):

# Optional replacement:
def generate_basic_nmstate_yaml(...):
    data = {
        "interfaces": [{
            "name": "eth0",
            "type": "ethernet",
            "state": "up",
            "ipv4": {
                "address": [{"ip": ip_address, "prefix-length": subnet_prefix_len}],
                "enabled": True,
                "dhcp": False,
            },
        }],
        "dns-resolver": {"config": {"server": [str(dns_server)]}},
        "routes": {"config": [{
            "destination": "0.0.0.0/0",
            "next-hop-address": gateway_address,
            "next-hop-interface": "eth0",
            "table-id": 254,
        }]},
    }
    # return yaml.safe_dump(data, sort_keys=False)
    from json import dumps  # if avoiding PyYAML, at least validate via dict first
    return dedent(
        f"""{dumps(data)}"""
    )  # or keep current YAML but build 'data' first to validate inputs

21-28: Add basic input validation for IP/prefix (fail fast).

Validate IPs and prefix length to avoid pushing invalid configs downstream.

+from ipaddress import ip_address as _ip, IPv4Address
@@
 def add_or_update_static_host_config(
@@
 ) -> list[HostStaticNetworkConfig]:
@@
+    # Basic sanity checks (IPv4-only template)
+    if not isinstance(subnet_prefix_len, int) or not (0 <= subnet_prefix_len <= 32):
+        raise ValueError("subnet_prefix_len must be 0..32")
+    if not isinstance(dns_server, str) or not isinstance(ip_address, str) or not isinstance(gateway_address, str):
+        raise ValueError("dns_server, ip_address, and gateway_address must be strings")
+    for v in (dns_server, ip_address, gateway_address):
+        if not isinstance(_ip(v), IPv4Address):
+            raise ValueError("Only IPv4 addresses are supported by the basic template")
tests/test_static_net.py (3)

1-1: Remove unnecessary type ignore.

Not needed for tests; can hide genuine issues.

-# type: ignore

204-240: Add remove() test with list input.

@@ class TestRemoveStaticHostConfig:
     def test_remove_existing_host_config(self):
@@
         assert result[0]["network_yaml"] == "yaml2"
+
+    def test_remove_with_existing_list_input(self):
+        existing_list = [
+            {"mac_interface_map": [{"logical_nic_name": "eth0", "mac_address": "00:11:22:33:44:55"}], "network_yaml": "yaml1"},
+            {"mac_interface_map": [{"logical_nic_name": "eth0", "mac_address": "aa:bb:cc:dd:ee:ff"}], "network_yaml": "yaml2"},
+        ]
+        result = remove_static_host_config(existing_static_network_config=existing_list, mac_address="00:11:22:33:44:55")
+        assert len(result) == 1
+        assert result[0]["mac_interface_map"][0]["mac_address"] == "aa:bb:cc:dd:ee:ff"

63-88: Add case-insensitive MAC matching test.

@@ class TestFindHostConfigIndexForMac:
     def test_find_mac_in_empty_list(self):
         """Test searching in an empty configuration list."""
         index = find_host_config_index_for_mac("00:11:22:33:44:55", [])
         assert index is None
+
+    def test_find_mac_case_insensitive(self):
+        configs = [
+            {
+                "mac_interface_map": [{"logical_nic_name": "eth0", "mac_address": "AA:BB:CC:DD:EE:FF"}],
+                "network_yaml": "yaml",
+            }
+        ]
+        assert find_host_config_index_for_mac("aa:bb:cc:dd:ee:ff", configs) == 0
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 604a584 and 3aec734.

📒 Files selected for processing (3)
  • server.py (2 hunks)
  • service_client/static_net.py (1 hunks)
  • tests/test_static_net.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
tests/test_static_net.py (1)
service_client/static_net.py (4)
  • add_or_update_static_host_config (21-60)
  • find_host_config_index_for_mac (63-75)
  • generate_basic_nmstate_yaml (78-106)
  • remove_static_host_config (109-127)
server.py (3)
metrics/metrics.py (3)
  • metrics (69-71)
  • track_tool_usage (51-65)
  • initiate_metrics (44-48)
service_client/assisted_service_api.py (3)
  • get_infra_env (203-218)
  • update_infra_env (306-327)
  • list_infra_envs (221-239)
service_client/static_net.py (2)
  • add_or_update_static_host_config (21-60)
  • remove_static_host_config (109-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Red Hat Konflux / assisted-service-mcp-saas-main-on-pull-request
🔇 Additional comments (2)
server.py (2)

18-24: LGTM on metrics/static_net imports.

Imports are consistent with usage below.


458-476: LGTM: list_infra_envs tool.

Straight-through listing with JSON response and helpful log.

Comment thread server.py
Comment thread server.py
Comment thread service_client/static_net.py
Comment thread service_client/static_net.py
Comment thread tests/test_static_net.py
Right now it is pretty basic with just the ability to add
basic network config with a predefined NMState template.

The API handling is a bit awkward given the MAC to IP mapping but
all of that complexity is hidden from the LLM.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
server.py (1)

432-456: Delete path looks correct with current helper types.

This now passes the string/None infra_env.static_network_config to remove_static_host_config(str|None), which aligns with the helpers and the API response behavior. The earlier concern about list inputs is addressed by the updated helper signatures.

🧹 Nitpick comments (3)
server.py (3)

18-24: Alias the metrics route to avoid shadowing and verify package re-exports.

Importing metrics from the metrics package may shadow the package name locally and relies on __init__.py re-exporting metrics, track_tool_usage, and initiate_metrics. Alias the route for clarity and confirm the re-exports exist; otherwise import from metrics.metrics.

-from metrics import metrics, track_tool_usage, initiate_metrics
+from metrics import metrics as metrics_endpoint, track_tool_usage, initiate_metrics

Apply outside this hunk:

-    app.add_route("/metrics", metrics)
+    app.add_route("/metrics", metrics_endpoint)

To verify exports:

  • Ensure metrics/init.py re-exports: from .metrics import track_tool_usage, initiate_metrics and exposes metrics.

405-424: Add basic IP and prefix validation to fail fast with clear errors.

Validate IP formats, family consistency, and prefix length before generating nmstate YAML to prevent opaque backend 4xx errors.

     client = InventoryClient(get_access_token())
     infra_env_id = await _get_cluster_infra_env_id(client, cluster_id)
     infra_env = await client.get_infra_env(infra_env_id)
 
+    # Validate addressing inputs early for clearer errors
+    import ipaddress
+    try:
+        ip = ipaddress.ip_address(ip_address)
+        gw = ipaddress.ip_address(gateway_address)
+        dns = ipaddress.ip_address(dns_server)
+    except ValueError as e:
+        raise ValueError(f"Invalid IP address input: {e}") from e
+    if ip.version != gw.version or ip.version != dns.version:
+        raise ValueError("IP, gateway, and DNS server must be the same IP family (IPv4 or IPv6).")
+    if ip.version == 4 and not (0 <= subnet_prefix_len <= 32):
+        raise ValueError("subnet_prefix_len must be between 0 and 32 for IPv4.")
+    if ip.version == 6 and not (0 <= subnet_prefix_len <= 128):
+        raise ValueError("subnet_prefix_len must be between 0 and 128 for IPv6.")
+
     static_network_config = add_or_update_static_host_config(
         existing_static_network_config=infra_env.static_network_config,
         dns_server=dns_server,
         mac_address=mac_address,
         ip_address=ip_address,
         gateway_address=gateway_address,
         subnet_prefix_len=subnet_prefix_len,
     )

458-476: LGTM; consider response shaping if output becomes noisy.

Returning raw infra-env dicts is fine. If this proves too verbose in the UI, optionally filter to key fields (e.g., id, name, static_network_config) or pretty-print with an indent for readability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3aec734 and 2223212.

📒 Files selected for processing (3)
  • server.py (2 hunks)
  • service_client/static_net.py (1 hunks)
  • tests/test_static_net.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/test_static_net.py
  • service_client/static_net.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-09T18:51:46.556Z
Learnt from: keitwb
PR: openshift-assisted/assisted-service-mcp#91
File: service_client/static_net.py:21-36
Timestamp: 2025-09-09T18:51:46.556Z
Learning: In the assisted-service API, the static_network_config field is typed as a list when input to the API but comes back out as a string in responses. Functions processing this field from API responses should handle string inputs only.

Applied to files:

  • server.py
🧬 Code graph analysis (1)
server.py (3)
metrics/metrics.py (3)
  • metrics (69-71)
  • track_tool_usage (51-65)
  • initiate_metrics (44-48)
service_client/assisted_service_api.py (3)
  • get_infra_env (203-218)
  • update_infra_env (306-327)
  • list_infra_envs (221-239)
service_client/static_net.py (2)
  • add_or_update_static_host_config (21-60)
  • remove_static_host_config (109-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Red Hat Konflux / assisted-service-mcp-saas-main-on-pull-request
🔇 Additional comments (1)
server.py (1)

417-424: Good: Handles API’s string-encoded static_network_config correctly.

Passing infra_env.static_network_config (string/None) into add_or_update_static_host_config that accepts str | None matches the Assisted Service response quirk noted in learnings. The helper returns a list suitable for update_infra_env.

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Sep 9, 2025

@keitwb: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/eval-test 2223212 link false /test eval-test

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Copy link
Copy Markdown
Collaborator

@eranco74 eranco74 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good,
Unsure about list_infra_envs tool.

Comment thread server.py

@mcp.tool()
@track_tool_usage()
async def list_infra_envs(cluster_id: str) -> str:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this tool?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that you can ask the bot what the current static networking config is for a given cluster.

@keitwb
Copy link
Copy Markdown
Contributor Author

keitwb commented Sep 17, 2025

Replaced by #96

@keitwb keitwb closed this Sep 17, 2025
@keitwb keitwb deleted the static-hosts branch September 17, 2025 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants