Skip to content

[codex] Add customer VM IPv6 provisioning#33

Merged
Svaag merged 4 commits into
mainfrom
fix/customer-vm-ipv6
Jul 1, 2026
Merged

[codex] Add customer VM IPv6 provisioning#33
Svaag merged 4 commits into
mainfrom
fix/customer-vm-ipv6

Conversation

@Svaag

@Svaag Svaag commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds tactical per-VM IPv6 provisioning for customer VMs so paid Debian VMs no longer wait for nonexistent RA/DHCPv6 on the customer segment.

Changes

  • Add DB-backed customer /64 allocation fields and Alembic revision 012.
  • Reserve prefix index 0; allocate VM prefixes from 2a0c:b641:b51::/48 and assign ::2 inside each /64.
  • Render Debian netplan v2 networkConfig for XO with enX0, DNS/gateway 2a0c:b641:b51::1, and on-link: true.
  • Pass optional networkConfig through XCPNGProvider.create_vm.
  • In real provisioning mode, expose/reject only OS templates with static network-config support for now.
  • Wait for XO guest metrics to report the expected assigned IPv6 before creating DNS and marking the VM ready.
  • Add ipv6_prefix to VM status responses.
  • Fix RFC2136 DNS updates to use absolute record names, avoiding duplicated names like vm.deploy.hyrule.host.deploy.hyrule.host.

Validation

  • uv run pytest passed: 213 tests before the DNS follow-up.
  • Focused retest after DNS follow-up: uv run pytest tests/test_dns_provider.py tests/test_customer_ipv6.py tests/test_xcpng_openbsd.py tests/test_vm_quote.py tests/test_api.py passed: 43 tests.
  • uv run mypy hyrule_cloud/ passed after the CI typing fix.
  • uvx ruff check on touched app/test/migration files passed.
  • GitHub Actions are green on this PR.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

PR Reviewer Guide 🔍

(Review updated until commit eb04d5a)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🏅 Score: 88
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Retry Loop

The retry loop in create_vm generates a new VM ID on each iteration. If the retry limit is exhausted, the RuntimeError is raised, but the VM ID of the last attempt is lost. Additionally, the loop catches IntegrityError broadly, which could mask other unique constraint violations unrelated to prefix collision (e.g., hostname collision), causing an unintended infinite loop scenario. A more robust approach would be to generate the VM ID once and only retry prefix allocation.

    vm_id = generate_vm_id()
    hostname_prefix = self._generate_hostname(vm_id)
    hostname = f"{hostname_prefix}.{self.config.deploy_domain}"

    async with self.db() as session:
        prefix_index, prefix = await self._allocate_customer_prefix(session, vm_id)
        row = VMRow(
            vm_id=vm_id,
            owner_wallet=owner_wallet,
            owner_account_id=owner_account_id,
            status=VMStatus.PROVISIONING,
            anon_management_token_hash=hash_anon_token(anon_token),
            size=request.size,
            os=request.os,
            ipv6=None,
            ipv6_prefix_index=prefix_index,
            ipv6_prefix=str(prefix),
            hostname=hostname,
            ssh_pubkey=request.ssh_pubkey,
            open_ports=[22] + [p for p in request.open_ports if p != 22],
            setup_script=request.setup_script,
            domain_mode=request.domain_mode,
            domain=request.domain,
            expires_at=expires_at,
            cost_total=total,
        )
        session.add(row)
        try:
            await session.commit()
        except IntegrityError:
            await session.rollback()
            log.warning("customer_ipv6_prefix_collision", vm_id=vm_id, prefix=str(prefix))
            continue
        # Refresh to get server defaults
        await session.refresh(row)
        break
else:
    raise RuntimeError("Could not allocate a unique customer IPv6 prefix")

Svaag commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Addressed PR-Agent feedback:

  • Added validation in render_debian_network_config for /64 prefixes, VM address membership, non-empty DNS, and gateway/DNS membership in the configured customer supernet. This is in commit eb04d5a.
  • The prefix-collision retry note about the background task is a false positive: the task is created only after the retry loop exits via a successful commit, so a failed IntegrityError attempt does not start provisioning.
  • The legacy non-quote idempotency note is pre-existing behavior and outside this IPv6 fix. The quote-bound path remains the intended idempotent paid create path.

@Svaag Svaag marked this pull request as ready for review July 1, 2026 22:57
@Svaag Svaag merged commit 9661a8a into main Jul 1, 2026
5 of 6 checks passed
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Persistent review updated to latest commit eb04d5a

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix prefix collision retry logic

The retry loop generates a new vm_id on each iteration, but
_allocate_customer_prefix uses vm_id to compute the starting prefix index. If a
collision occurs, the next iteration will likely try the same prefix again because
the used set is not refreshed from the database after the rollback. This can lead to
an infinite loop or premature failure. Refresh the used set by re-querying the
database after each rollback, or move the prefix allocation outside the retry loop
and only retry on a different index.

hyrule_cloud/orchestrator.py [166-204]

+for _ in range(5):
+    vm_id = generate_vm_id()
+    hostname_prefix = self._generate_hostname(vm_id)
+    hostname = f"{hostname_prefix}.{self.config.deploy_domain}"
 
+    async with self.db() as session:
+        prefix_index, prefix = await self._allocate_customer_prefix(session, vm_id)
+        ...
+        session.add(row)
+        try:
+            await session.commit()
+        except IntegrityError:
+            await session.rollback()
+            log.warning("customer_ipv6_prefix_collision", vm_id=vm_id, prefix=str(prefix))
+            continue
+        await session.refresh(row)
+        break
+else:
+    raise RuntimeError("Could not allocate a unique customer IPv6 prefix")
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that after a rollback due to an IntegrityError, the used set in _allocate_customer_prefix is stale, causing the retry to likely use the same prefix. This is a real bug that could cause infinite retries or premature failure, impacting reliability. The score is high because it addresses a significant runtime issue, but not a security vulnerability.

Medium

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb04d5a785

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if port in cfg.blocked_ports:
raise HTTPException(400, f"Port {port} is blocked by policy")

if _real_provisioning_enabled() and not supports_static_network_config(order.os):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Apply real-mode OS validation to native intents

When real provisioning is enabled, this OS support check is only reached by /v1/vm/create and /v1/vm/quote; /v1/intent/create still does its own domain/blocked-port validation and then stores any order_payload. If BTC/XMR payments are configured, a customer can open an intent for openbsd-7.8/alpine, receive a deposit address, pay, and only then have _provision_vm fail with Static network config is not supported, while the x402 paths correctly reject before payment. Please route native intent creation through the same helper before creating the intent.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant