Skip to content
This repository was archived by the owner on Sep 9, 2026. It is now read-only.

OSAC-2341: create default networking resources at tenant onboarding - #964

Merged
openshift-merge-bot[bot] merged 5 commits into
osac-project:mainfrom
danmanor:worktree-OSAC-2341-default-networking-onboarding
Jul 29, 2026
Merged

openshift-merge-bot[bot] merged 5 commits into
osac-project:mainfrom
danmanor:worktree-OSAC-2341-default-networking-onboarding

Conversation

@danmanor

@danmanor danmanor commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add DefaultNetworkingProvisioner that creates default networking resources (VirtualNetwork, IPv4 Subnet, IPv6 Subnet, SecurityGroup, and optionally ExternalIP + NATGateway) when a tenant is created
  • Resources are created via DAOs directly to bypass server-level async preconditions (VN must be READY for Subnet, ExternalIP must be ALLOCATED for NATGateway) — all start in PENDING state and are reconciled by the operator
  • Wire provisioner into PrivateTenantsServer.Create as an optional dependency

Jira

https://redhat.atlassian.net/browse/OSAC-2341

Design

Default Networking EP — §Tenant Onboarding

Changes

New file: internal/servers/default_networking_provisioner.go

  • DefaultNetworkingProvisioner with builder pattern
  • Provision(ctx, tenantName) — reads default NetworkClass, creates VN/Subnets/SG/NATGateway
  • All resources labeled osac.openshift.io/default: "true" with correct tenant annotation
  • Subnets and SG get osac.openshift.io/owner-reference annotation pointing to VN
  • ExternalIP pool selection: READY pool with most available capacity
  • NATGateway auto-allocated ExternalIP with pool capacity decrement

Modified: internal/servers/private_tenants_server.go

  • Added optional DefaultNetworkingProvisioner dependency via SetDefaultNetworkingProvisioner()
  • Create() calls Provision() after successful tenant creation (same DB transaction)

Modified: internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go

  • Wire provisioner into private tenants server at startup

Test plan

  • 10 unit tests for DefaultNetworkingProvisioner covering:
    • Happy path: VN + IPv4/IPv6 Subnets + SG with correct labels, annotations, CIDRs, state
    • NATGateway skipped when enable_nat_gateway is false
    • Implementation strategy propagation from NetworkClass
    • No-op when no default NetworkClass exists
    • No-op when NetworkClass has no defaults
    • Partial defaults (IPv4-only)
  • 2 unit tests for tenant server wiring:
    • Tenant Create with provisioner creates default resources
    • Tenant Create without provisioner preserves existing behavior
  • Full servers test suite passes (1410 specs)
  • Full unit test suite passes (80 suites across all internal/ packages)
  • golangci-lint: 0 issues
  • gofmt: no formatting changes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Newly created tenants can automatically receive default networking resources (virtual network, security group, and IPv4/IPv6 subnets when configured).
    • Optional NAT gateway and external IP attachment are provisioned when NAT is enabled and capacity is available.
    • Defaults are applied only when a default network configuration exists.
  • Bug Fixes
    • Tenant creation returns an internal error if default networking provisioning fails.
    • The gRPC server now refuses to start if the default networking provisioner cannot be constructed.
  • Tests
    • Added coverage for default, partial, and missing defaults, plus NAT capacity/error scenarios.

@openshift-ci-robot

openshift-ci-robot commented Jul 27, 2026 •

Copy link
Copy Markdown

@danmanor: This pull request references OSAC-2341 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

  • Add DefaultNetworkingProvisioner that creates default networking resources (VirtualNetwork, IPv4 Subnet, IPv6 Subnet, SecurityGroup, and optionally ExternalIP + NATGateway) when a tenant is created
  • Resources are created via DAOs directly to bypass server-level async preconditions (VN must be READY for Subnet, ExternalIP must be ALLOCATED for NATGateway) — all start in PENDING state and are reconciled by the operator
  • Wire provisioner into PrivateTenantsServer.Create as an optional dependency

Jira

https://redhat.atlassian.net/browse/OSAC-2341

Design

Default Networking EP — §Tenant Onboarding

Changes

New file: internal/servers/default_networking_provisioner.go

  • DefaultNetworkingProvisioner with builder pattern
  • Provision(ctx, tenantName) — reads default NetworkClass, creates VN/Subnets/SG/NATGateway
  • All resources labeled osac.openshift.io/default: "true" with correct tenant annotation
  • Subnets and SG get osac.openshift.io/owner-reference annotation pointing to VN
  • ExternalIP pool selection: READY pool with most available capacity
  • NATGateway auto-allocated ExternalIP with pool capacity decrement

Modified: internal/servers/private_tenants_server.go

  • Added optional DefaultNetworkingProvisioner dependency via SetDefaultNetworkingProvisioner()
  • Create() calls Provision() after successful tenant creation (same DB transaction)

Modified: internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go

  • Wire provisioner into private tenants server at startup

Test plan

  • 10 unit tests for DefaultNetworkingProvisioner covering:
  • Happy path: VN + IPv4/IPv6 Subnets + SG with correct labels, annotations, CIDRs, state
  • NATGateway skipped when enable_nat_gateway is false
  • Implementation strategy propagation from NetworkClass
  • No-op when no default NetworkClass exists
  • No-op when NetworkClass has no defaults
  • Partial defaults (IPv4-only)
  • 2 unit tests for tenant server wiring:
  • Tenant Create with provisioner creates default resources
  • Tenant Create without provisioner preserves existing behavior
  • Full servers test suite passes (1410 specs)
  • Full unit test suite passes (80 suites across all internal/ packages)
  • golangci-lint: 0 issues
  • gofmt: no formatting changes

🤖 Generated with Claude Code

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 openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci

openshift-ci Bot commented Jul 27, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: danmanor

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

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds a default networking provisioner that creates tenant networking resources from NetworkClass defaults, invokes it after private tenant creation, and wires it into gRPC server startup with coverage for configured and absent defaults.

Changes

Default tenant networking

Layer / File(s) Summary
Default resource provisioning
internal/servers/default_networking_provisioner.go, internal/servers/default_networking_provisioner_test.go
Builds networking DAOs and provisions default virtual networks, IPv4/IPv6 subnets, security groups, and optional NAT resources based on NetworkClass defaults, including capacity updates and absent or partial defaults.
Tenant creation integration
internal/servers/private_tenants_server.go, internal/servers/private_tenants_server_test.go
Injects the provisioner into PrivateTenantsServer, invokes it after tenant creation, and returns an Internal error when provisioning fails.
Server startup wiring
internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go
Constructs the provisioner with the logger, tenancy logic, and metrics registerer, then injects it into the private tenants server.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: do-not-merge/work-in-progress

Suggested reviewers: tzumainn, crystalchun

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant PrivateTenantsServer
  participant DefaultNetworkingProvisioner
  participant NetworkClassDAO
  participant NetworkingDAOs
  Client->>PrivateTenantsServer: Create tenant
  PrivateTenantsServer->>DefaultNetworkingProvisioner: Provision(ctx, tenantName)
  DefaultNetworkingProvisioner->>NetworkClassDAO: Find default NetworkClass
  NetworkClassDAO-->>DefaultNetworkingProvisioner: Return defaults or none
  DefaultNetworkingProvisioner->>NetworkingDAOs: Create default VN, subnets, security group, and optional NAT
  NetworkingDAOs-->>DefaultNetworkingProvisioner: Persisted resources or error
  DefaultNetworkingProvisioner-->>PrivateTenantsServer: Provisioning result
  PrivateTenantsServer-->>Client: Tenant response or Internal error
Loading

Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error New Info/Error logs emit tenant names (user-supplied customer identifiers) in default-networking provisioning and tenant-create failure paths. Stop logging tenant names/raw request-derived identifiers; use opaque IDs or omit those fields from these log entries.
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: provisioning default networking resources during tenant onboarding.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No-Hardcoded-Secrets ✅ Passed No hardcoded secrets in changed files; only generated break-glass password, no credential-shaped literals, embedded creds, or long base64 blobs were added.
No-Weak-Crypto ✅ Passed No weak-crypto primitives or insecure secret comparisons were added; the only crypto usage is crypto/rand for password generation.
No-Injection-Vectors ✅ Passed Touched Go files only use DAO builders and structured fields; no eval/exec, shell, pickle, yaml.load, os.system, or unsafe HTML/SQL interpolation found.
Container-Privileges ✅ Passed No changed K8s/container manifest adds privileged/host* settings, SYS_ADMIN, root, or allowPrivilegeEscalation=true; scan found none.
Ai-Attribution ✅ Passed PR mentions Claude Code; the branch commits use Red Hat-style 'Assisted-by: Claude Code' trailers, with no AI-related Co-Authored-By found.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@danmanor danmanor changed the title OSAC-2341: create default networking resources at tenant onboarding WIP: OSAC-2341: create default networking resources at tenant onboarding Jul 27, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/servers/default_networking_provisioner_test.go`:
- Line 154: Update the owner-reference assertions in the affected networking
provisioner tests to capture the VN ID from the earlier list and use
HaveKeyWithValue for "osac.openshift.io/owner-reference" with that vnID,
replacing the key-only HaveKey checks at all three locations.
- Around line 303-313: Remove the boolPtr, int32Ptr, and stringPtr helper
functions, and replace their call sites with Go 1.26 new expressions using the
corresponding typed expressions where needed, preserving the existing pointer
values and behavior.
- Around line 212-221: The NAT gateway provisioning path lacks coverage beyond
the disabled case. Add specs around provisionNATGateway that exercise an
eligible external IP pool and verify ExternalIP and NATGateway creation,
allocated/available capacity changes, and attached=true; also add a
no-eligible-pool case asserting the error from
findExternalIPPool/provisionNATGateway propagates.

In `@internal/servers/default_networking_provisioner.go`:
- Around line 231-268: The five default resource creation helpers duplicate
metadata construction and should share a common defaultMetadata(tenantName,
name, ownerID string) helper. Add that helper to centralize the default label,
system creator, and optional owner-reference annotation, then update
createDefaultVirtualNetwork and the corresponding create helpers to use it while
preserving each resource’s existing DAO creation and ID return behavior.
- Around line 325-330: Update the SecurityGroupSpec builder in the default
networking provisioner to use the NetworkClass implementation strategy, via the
same nc.GetImplementationStrategy() value propagated to the VirtualNetwork,
instead of hardcoding "network_policy". Keep the ingress, egress, and virtual
network assignments unchanged.
- Around line 195-224: Make
internal/servers/default_networking_provisioner.go#L195-L224 explicitly document
on the exported Provision method that its dependent networking writes must run
inside a caller-supplied PostgreSQL transaction; add the corresponding brief
comment at the Provision call site in
internal/servers/private_tenants_server.go#L209-L223 stating that tenant
insertion and networking writes share the request transaction and roll back
together when the RPC returns Internal. Do not add resource-level rollback or
idempotency beyond this documentation.
- Around line 79-82: Remove the unused no-op newDAO closure and its discard
assignment from Build, leaving the surrounding provisioning logic unchanged.
- Around line 446-464: Update updatePoolCapacity to initialize and attach an
ExternalIPPool status object when pool.GetStatus() is nil before mutating
allocation fields. Also validate the resulting available capacity under the
locked pool state and return an error without updating when the decrement would
make it negative; preserve the existing wrapped errors for DAO operations.

In `@internal/servers/private_tenants_server_test.go`:
- Around line 484-506: Add a test under the “with default networking
provisioner” context that configures a NetworkClass with defaults forcing
provisioning failure, such as enable_nat_gateway without an eligible ExternalIP
pool, then exercises PrivateTenantsServer.Internal and asserts the returned
error code and message from the new failure branch. Reuse the existing
provisionerServer setup and test fixtures.
- Around line 551-569: Add per-spec database isolation around the setup used by
the Create test suite, ensuring each BeforeEach transaction rolls back or resets
the schema/container state before the next spec runs. Preserve the “no
NetworkClass” precondition in the “creates tenant without default networking
when no NetworkClass exists” spec so its VirtualNetwork assertion is independent
of prior specs.

In `@internal/servers/private_tenants_server.go`:
- Around line 209-223: Add a concise contract comment near the default
networking Provision call in the tenant creation flow, or on
DefaultNetworkingProvisioner.Provision, stating that atomic cleanup depends on
the surrounding single PostgreSQL transaction and callers outside that
transaction must not assume rollback of partial provisioning. Keep the existing
error handling unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 852cb5f8-cd9d-43f0-88a9-479eb163a0ba

📥 Commits

Reviewing files that changed from the base of the PR and between 9b4c4d5 and 30c303e.

📒 Files selected for processing (5)
  • internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go
  • internal/servers/default_networking_provisioner.go
  • internal/servers/default_networking_provisioner_test.go
  • internal/servers/private_tenants_server.go
  • internal/servers/private_tenants_server_test.go

Comment thread internal/servers/default_networking_provisioner_test.go Outdated
Comment thread internal/servers/default_networking_provisioner_test.go
Comment thread internal/servers/default_networking_provisioner_test.go Outdated
Comment thread internal/servers/default_networking_provisioner.go Outdated
Comment thread internal/servers/default_networking_provisioner.go
Comment on lines +325 to +330
Spec: privatev1.SecurityGroupSpec_builder{
VirtualNetwork: vnID,
Ingress: defaults.GetIngressRules(),
Egress: defaults.GetEgressRules(),
ImplementationStrategy: "network_policy",
}.Build(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

implementation_strategy hardcoded on the SecurityGroup while the VirtualNetwork gets it from the NetworkClass.

Line 249 propagates nc.GetImplementationStrategy(); here it's pinned to "network_policy". If a NetworkClass declares netris, the VN and its SecurityGroup disagree. Intentional?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/default_networking_provisioner.go` around lines 325 - 330,
Update the SecurityGroupSpec builder in the default networking provisioner to
use the NetworkClass implementation strategy, via the same
nc.GetImplementationStrategy() value propagated to the VirtualNetwork, instead
of hardcoding "network_policy". Keep the ingress, egress, and virtual network
assignments unchanged.

Comment on lines +446 to +464
func (p *DefaultNetworkingProvisioner) updatePoolCapacity(ctx context.Context, poolID string, delta int64) error {
getResponse, err := p.externalIPPoolDao.Get().
SetId(poolID).
SetLock(true).
Do(ctx)
if err != nil {
return fmt.Errorf("failed to get ExternalIPPool for capacity update: %w", err)
}

pool := getResponse.GetObject()
pool.GetStatus().SetAllocated(pool.GetStatus().GetAllocated() + delta)
pool.GetStatus().SetAvailable(pool.GetStatus().GetAvailable() - delta)

_, err = p.externalIPPoolDao.Update().SetObject(pool).Do(ctx)
if err != nil {
return fmt.Errorf("failed to update ExternalIPPool capacity: %w", err)
}
return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unguarded status mutation and unchecked capacity floor.

Two problems:

  1. pool.GetStatus() returns nil when the pool has no status set; the subsequent SetAllocated call on a nil status message panics.
  2. available is only checked in the CEL filter at selection time (Line 377). Between selection and this update nothing re-validates it, so available can be driven negative.
🛡️ Proposed fix
 	pool := getResponse.GetObject()
+	if !pool.HasStatus() {
+		return fmt.Errorf("ExternalIPPool '%s' has no status", poolID)
+	}
+	if pool.GetStatus().GetAvailable() < delta {
+		return fmt.Errorf(
+			"ExternalIPPool '%s' has %d available addresses, need %d",
+			poolID, pool.GetStatus().GetAvailable(), delta,
+		)
+	}
 	pool.GetStatus().SetAllocated(pool.GetStatus().GetAllocated() + delta)
 	pool.GetStatus().SetAvailable(pool.GetStatus().GetAvailable() - delta)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (p *DefaultNetworkingProvisioner) updatePoolCapacity(ctx context.Context, poolID string, delta int64) error {
getResponse, err := p.externalIPPoolDao.Get().
SetId(poolID).
SetLock(true).
Do(ctx)
if err != nil {
return fmt.Errorf("failed to get ExternalIPPool for capacity update: %w", err)
}
pool := getResponse.GetObject()
pool.GetStatus().SetAllocated(pool.GetStatus().GetAllocated() + delta)
pool.GetStatus().SetAvailable(pool.GetStatus().GetAvailable() - delta)
_, err = p.externalIPPoolDao.Update().SetObject(pool).Do(ctx)
if err != nil {
return fmt.Errorf("failed to update ExternalIPPool capacity: %w", err)
}
return nil
}
func (p *DefaultNetworkingProvisioner) updatePoolCapacity(ctx context.Context, poolID string, delta int64) error {
getResponse, err := p.externalIPPoolDao.Get().
SetId(poolID).
SetLock(true).
Do(ctx)
if err != nil {
return fmt.Errorf("failed to get ExternalIPPool for capacity update: %w", err)
}
pool := getResponse.GetObject()
if !pool.HasStatus() {
return fmt.Errorf("ExternalIPPool '%s' has no status", poolID)
}
if pool.GetStatus().GetAvailable() < delta {
return fmt.Errorf(
"ExternalIPPool '%s' has %d available addresses, need %d",
poolID, pool.GetStatus().GetAvailable(), delta,
)
}
pool.GetStatus().SetAllocated(pool.GetStatus().GetAllocated() + delta)
pool.GetStatus().SetAvailable(pool.GetStatus().GetAvailable() - delta)
_, err = p.externalIPPoolDao.Update().SetObject(pool).Do(ctx)
if err != nil {
return fmt.Errorf("failed to update ExternalIPPool capacity: %w", err)
}
return nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/default_networking_provisioner.go` around lines 446 - 464,
Update updatePoolCapacity to initialize and attach an ExternalIPPool status
object when pool.GetStatus() is nil before mutating allocation fields. Also
validate the resulting available capacity under the locked pool state and return
an error without updating when the decrement would make it negative; preserve
the existing wrapped errors for DAO operations.

Comment on lines +484 to +506
Context("with default networking provisioner", func() {
var (
provisionerServer *PrivateTenantsServer
provisioner *DefaultNetworkingProvisioner
)

BeforeEach(func() {
var err error
provisioner, err = NewDefaultNetworkingProvisioner().
SetLogger(logger).
SetTenancyLogic(tenancy).
Build()
Expect(err).ToNot(HaveOccurred())

provisionerServer, err = NewPrivateTenantsServer().
SetLogger(logger).
SetAttributionLogic(attribution).
SetTenancyLogic(tenancy).
SetDefaultNetworkingProvisioner(provisioner).
Build()
Expect(err).ToNot(HaveOccurred())
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing coverage for the failure branch.

The new Internal error path in private_tenants_server.go Lines 213-222 has no test. A provisioner pointed at a NetworkClass whose defaults force a failure (e.g. enable_nat_gateway with no eligible ExternalIP pool) would cover both the error code and the message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/private_tenants_server_test.go` around lines 484 - 506, Add
a test under the “with default networking provisioner” context that configures a
NetworkClass with defaults forcing provisioning failure, such as
enable_nat_gateway without an eligible ExternalIP pool, then exercises
PrivateTenantsServer.Internal and asserts the returned error code and message
from the new failure branch. Reuse the existing provisionerServer setup and test
fixtures.

Comment on lines +551 to +569
It("creates tenant without default networking when no NetworkClass exists", func() {
request := privatev1.TenantsCreateRequest_builder{
Object: privatev1.Tenant_builder{
Metadata: privatev1.Metadata_builder{
Name: "plain-tenant",
}.Build(),
}.Build(),
}.Build()

response, err := provisionerServer.Create(ctx, request)
Expect(err).ToNot(HaveOccurred())
Expect(response).ToNot(BeNil())

vnList, err := provisioner.virtualNetworkDao.List().
SetFilter("this.metadata.tenant == 'plain-tenant'").
Do(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(vnList.GetItems()).To(BeEmpty())
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'suite_test.go' internal/servers --exec cat -n {}

Repository: osac-project/fulfillment-service

Length of output: 4584


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Candidate test file size:"
wc -l internal/servers/private_tenants_server_test.go

echo "Suite hooks:"
rg -n "Before(Suite|Each|All)|After(Suite|Each|All)|DeferCleanup|RunSpecs|BeforeEach|AfterEach" internal/servers/private_tenants_server_test.go internal/servers/suite_test.go

echo "Relevant preceding spec area:"
sed -n '520,580p' internal/servers/private_tenants_server_test.go | cat -n

echo "Virtual network creation paths in internal/servers/private_tenants_server_test.go:"
rg -n "NetworkClass|plain-tenant|default networking|VirtualNetwork|virtualNetworkDao|List\\(|Create\\(" internal/servers/private_tenants_server_test.go

echo "Broader test cleanup patterns in repo:"
rg -n "tx\\.Rollback|Rollback\\(ctx\\)|TxIntoContext|AfterEach\\(func|BeforeEach\\(func|DeferCleanup\\(func\\{" -g '*_test.go' internal | head -200

Repository: osac-project/fulfillment-service

Length of output: 482


Use per-spec database resets or rollback cleanup.

BeforeEach starts a fresh transaction from the same database.Container, with no rollback or schema reset, so any NetworkClass created by previous specs persists in shared DB state across the suite. Add per-spec cleanup/rollback before relying on invariants like “no VirtualNetwork for plain-tenant”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/private_tenants_server_test.go` around lines 551 - 569, Add
per-spec database isolation around the setup used by the Create test suite,
ensuring each BeforeEach transaction rolls back or resets the schema/container
state before the next spec runs. Preserve the “no NetworkClass” precondition in
the “creates tenant without default networking when no NetworkClass exists” spec
so its VirtualNetwork assertion is independent of prior specs.

Comment on lines +209 to +223
if err != nil {
return
}

if s.defaultNetworking != nil {
if provisionErr := s.defaultNetworking.Provision(ctx, name); provisionErr != nil {
s.logger.ErrorContext(ctx, "Failed to provision default networking",
slog.String("tenant", name),
slog.Any("error", provisionErr))
err = grpcstatus.Errorf(grpccodes.Internal,
"failed to provision default networking resources: %v", provisionErr)
return
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Atomicity here rests on an unstated assumption.

Returning Internal cleans up the half-created tenant only because the gRPC startup wraps each RPC in a single database transaction. That's correct today, but nothing in this file or in DefaultNetworkingProvisioner says so, and Provision is a public method that a future caller (a reconciler, a CLI, a background job) could invoke outside a transaction — at which point a mid-way failure leaves orphaned PENDING networking with no owner. A short comment here, or a doc note on Provision, would pin the contract down.

Based on learnings: the grpcserver startup configures an interceptor chain that wraps each incoming RPC handler in a single PostgreSQL transaction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/private_tenants_server.go` around lines 209 - 223, Add a
concise contract comment near the default networking Provision call in the
tenant creation flow, or on DefaultNetworkingProvisioner.Provision, stating that
atomic cleanup depends on the surrounding single PostgreSQL transaction and
callers outside that transaction must not assume rollback of partial
provisioning. Keep the existing error handling unchanged.

Source: Learnings

@danmanor

Copy link
Copy Markdown
Contributor Author

/retest

@github-actions

Copy link
Copy Markdown

Re-triggered failed runs:

  • E2E BMaaS Full Install (#30265094812)

@danmanor

Copy link
Copy Markdown
Contributor Author

/retest

@github-actions

Copy link
Copy Markdown

Re-triggered failed runs:

  • E2E BMaaS Full Install (#30265094812)

danmanor added 3 commits July 29, 2026 10:12
Create default networking resources (VirtualNetwork, IPv4/IPv6 Subnets,
SecurityGroup, and optionally ExternalIP + NATGateway) when a tenant is
created. Resources are created via DAOs directly to bypass server-level
async preconditions (VN must be READY for Subnet, ExternalIP must be
ALLOCATED for NATGateway). All resources start in PENDING state and are
reconciled by the operator.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Dan Manor <dmanor@redhat.com>
Add DefaultNetworkingProvisioner as an optional dependency on
PrivateTenantsServer. When set, Provision() is called after tenant
creation within the same DB transaction. Wire provisioner in the
grpc server startup.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Dan Manor <dmanor@redhat.com>
- Remove dead newDAO stub from Build()
- Add transaction contract doc on Provision()
- Assert owner-reference values (not just key presence) in tests
- Add NATGateway positive test (pool capacity, attached flag) and
  no-pool error test
- Replace boolPtr/int32Ptr/stringPtr helpers with new() expressions
- Fix ExternalIP pool selection to filter in Go (CEL filter cannot
  compare proto enum fields as strings)

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Dan Manor <dmanor@redhat.com>
@danmanor
danmanor force-pushed the worktree-OSAC-2341-default-networking-onboarding branch from ae32894 to 83c94a9 Compare July 29, 2026 07:19

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

♻️ Duplicate comments (4)
internal/servers/default_networking_provisioner.go (3)

324-329: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

implementation_strategy still hardcoded on SecurityGroup while VirtualNetwork gets it from NetworkClass.

Line 248 propagates nc.GetImplementationStrategy() to the VN; the SecurityGroup at line 328 is pinned to "network_policy", so a netris NetworkClass produces a VN/SecurityGroup pair with disagreeing strategies. This was flagged previously and remains unresolved.

🐛 Proposed fix
 func (p *DefaultNetworkingProvisioner) createDefaultSecurityGroup(
 	ctx context.Context,
-	tenantName, vnID string,
+	tenantName, vnID, implementationStrategy string,
 	defaults *privatev1.NetworkDefaults,
 ) (string, error) {
 	...
 		Spec: privatev1.SecurityGroupSpec_builder{
 			VirtualNetwork:         vnID,
 			Ingress:                defaults.GetIngressRules(),
 			Egress:                 defaults.GetEgressRules(),
-			ImplementationStrategy: "network_policy",
+			ImplementationStrategy: implementationStrategy,
 		}.Build(),

And update the call site in Provision to pass nc.GetImplementationStrategy().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/default_networking_provisioner.go` around lines 324 - 329,
Update the SecurityGroupSpec construction in Provision to use the NetworkClass
implementation strategy instead of the hardcoded "network_policy" value, and
pass nc.GetImplementationStrategy() through the relevant call path so the
VirtualNetwork and SecurityGroup strategies remain consistent.

230-267: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Five near-identical create helpers still duplicated.

Each helper builds metadata (default label, system creator, optional owner-reference annotation), calls <dao>.Create().SetObject(x).Do(ctx), and returns resp.GetObject().GetId(). A shared defaultMetadata(tenantName, name, ownerID string) *privatev1.Metadata helper removes the copy-paste. Flagged previously, still open.

Also applies to: 269-306, 307-341, 397-424, 425-453

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/default_networking_provisioner.go` around lines 230 - 267,
Extract the repeated metadata construction from createDefaultVirtualNetwork and
the other four default create helpers into a shared defaultMetadata(tenantName,
name, ownerID string) helper returning *privatev1.Metadata. Preserve the default
label, system creator, and optional owner-reference annotation behavior, then
update each helper to reuse it while leaving their DAO creation and ID return
flows unchanged.

454-472: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Pool capacity update still unguarded against nil status and negative availability.

pool.GetStatus() returns nil for a pool without status, so SetAllocated on line 464 panics. Nothing re-validates available >= delta between selection (line 384-386) and this update, so available can go negative. Same finding as the prior review, still unaddressed.

🛡️ Proposed fix
 	pool := getResponse.GetObject()
+	if !pool.HasStatus() {
+		return fmt.Errorf("ExternalIPPool '%s' has no status", poolID)
+	}
+	if pool.GetStatus().GetAvailable() < delta {
+		return fmt.Errorf(
+			"ExternalIPPool '%s' has %d available addresses, need %d",
+			poolID, pool.GetStatus().GetAvailable(), delta,
+		)
+	}
 	pool.GetStatus().SetAllocated(pool.GetStatus().GetAllocated() + delta)
 	pool.GetStatus().SetAvailable(pool.GetStatus().GetAvailable() - delta)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/default_networking_provisioner.go` around lines 454 - 472,
Update updatePoolCapacity to safely initialize or reject a nil pool status
before accessing allocation fields, and validate that the current available
capacity is at least delta before applying the update. Return a descriptive
error without persisting changes when the status is missing or availability
would become negative; preserve the existing locked fetch and update error
handling.
internal/servers/private_tenants_server_test.go (1)

484-570: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

No coverage for the provisioning-failure branch.

PrivateTenantsServer.Create now returns Internal when Provision fails (private_tenants_server.go lines 213-221), but this context only exercises the success and no-default-NetworkClass paths. Add a spec that forces Provision to fail (e.g. enable_nat_gateway: true with no eligible ExternalIPPool) and assert the returned Internal code/message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/private_tenants_server_test.go` around lines 484 - 570, Add
a spec in the “with default networking provisioner” context that creates a
default NetworkClass requiring NAT (enable_nat_gateway true) without an eligible
ExternalIPPool, then calls PrivateTenantsServer.Create and asserts it returns an
Internal error with the expected provisioning-failure message. Reuse the
existing provisionerServer setup and verify the failure branch in Create rather
than asserting successful tenant provisioning.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/servers/default_networking_provisioner.go`:
- Around line 69-163: Refactor DefaultNetworkingProvisionerBuilder.Build by
introducing a local or private generic helper that constructs each DAO with the
shared logger, tenancy logic, metrics registerer, and Build chain. Replace the
repeated construction blocks for networkClassDao, virtualNetworkDao, subnetDao,
securityGroupDao, externalIPDao, externalIPPoolDao, natGatewayDao, and tenantDao
with helper calls, preserving existing error propagation and result
initialization.

---

Duplicate comments:
In `@internal/servers/default_networking_provisioner.go`:
- Around line 324-329: Update the SecurityGroupSpec construction in Provision to
use the NetworkClass implementation strategy instead of the hardcoded
"network_policy" value, and pass nc.GetImplementationStrategy() through the
relevant call path so the VirtualNetwork and SecurityGroup strategies remain
consistent.
- Around line 230-267: Extract the repeated metadata construction from
createDefaultVirtualNetwork and the other four default create helpers into a
shared defaultMetadata(tenantName, name, ownerID string) helper returning
*privatev1.Metadata. Preserve the default label, system creator, and optional
owner-reference annotation behavior, then update each helper to reuse it while
leaving their DAO creation and ID return flows unchanged.
- Around line 454-472: Update updatePoolCapacity to safely initialize or reject
a nil pool status before accessing allocation fields, and validate that the
current available capacity is at least delta before applying the update. Return
a descriptive error without persisting changes when the status is missing or
availability would become negative; preserve the existing locked fetch and
update error handling.

In `@internal/servers/private_tenants_server_test.go`:
- Around line 484-570: Add a spec in the “with default networking provisioner”
context that creates a default NetworkClass requiring NAT (enable_nat_gateway
true) without an eligible ExternalIPPool, then calls PrivateTenantsServer.Create
and asserts it returns an Internal error with the expected provisioning-failure
message. Reuse the existing provisionerServer setup and verify the failure
branch in Create rather than asserting successful tenant provisioning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2b597fe6-e72b-40dd-acd3-7b0a86a6f250

📥 Commits

Reviewing files that changed from the base of the PR and between 30c303e and 83c94a9.

📒 Files selected for processing (5)
  • internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go
  • internal/servers/default_networking_provisioner.go
  • internal/servers/default_networking_provisioner_test.go
  • internal/servers/private_tenants_server.go
  • internal/servers/private_tenants_server_test.go

Comment on lines +69 to +163
func (b *DefaultNetworkingProvisionerBuilder) Build() (result *DefaultNetworkingProvisioner, err error) {
if b.logger == nil {
err = errors.New("logger is mandatory")
return
}
if b.tenancyLogic == nil {
err = errors.New("tenancy logic is mandatory")
return
}

networkClassDao, err := dao.NewGenericDAO[*privatev1.NetworkClass]().
SetLogger(b.logger).
SetTenancyLogic(b.tenancyLogic).
SetMetricsRegisterer(b.metricsRegisterer).
Build()
if err != nil {
return
}

virtualNetworkDao, err := dao.NewGenericDAO[*privatev1.VirtualNetwork]().
SetLogger(b.logger).
SetTenancyLogic(b.tenancyLogic).
SetMetricsRegisterer(b.metricsRegisterer).
Build()
if err != nil {
return
}

subnetDao, err := dao.NewGenericDAO[*privatev1.Subnet]().
SetLogger(b.logger).
SetTenancyLogic(b.tenancyLogic).
SetMetricsRegisterer(b.metricsRegisterer).
Build()
if err != nil {
return
}

securityGroupDao, err := dao.NewGenericDAO[*privatev1.SecurityGroup]().
SetLogger(b.logger).
SetTenancyLogic(b.tenancyLogic).
SetMetricsRegisterer(b.metricsRegisterer).
Build()
if err != nil {
return
}

externalIPDao, err := dao.NewGenericDAO[*privatev1.ExternalIP]().
SetLogger(b.logger).
SetTenancyLogic(b.tenancyLogic).
SetMetricsRegisterer(b.metricsRegisterer).
Build()
if err != nil {
return
}

externalIPPoolDao, err := dao.NewGenericDAO[*privatev1.ExternalIPPool]().
SetLogger(b.logger).
SetTenancyLogic(b.tenancyLogic).
SetMetricsRegisterer(b.metricsRegisterer).
Build()
if err != nil {
return
}

natGatewayDao, err := dao.NewGenericDAO[*privatev1.NATGateway]().
SetLogger(b.logger).
SetTenancyLogic(b.tenancyLogic).
SetMetricsRegisterer(b.metricsRegisterer).
Build()
if err != nil {
return
}

tenantDao, err := dao.NewGenericDAO[*privatev1.Tenant]().
SetLogger(b.logger).
SetTenancyLogic(b.tenancyLogic).
SetMetricsRegisterer(b.metricsRegisterer).
Build()
if err != nil {
return
}

result = &DefaultNetworkingProvisioner{
logger: b.logger,
networkClassDao: networkClassDao,
virtualNetworkDao: virtualNetworkDao,
subnetDao: subnetDao,
securityGroupDao: securityGroupDao,
externalIPDao: externalIPDao,
externalIPPoolDao: externalIPPoolDao,
natGatewayDao: natGatewayDao,
tenantDao: tenantDao,
}
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build repeats the same 4-line DAO construction 7 times.

Every DAO (networkClassDao, virtualNetworkDao, subnetDao, securityGroupDao, externalIPDao, externalIPPoolDao, natGatewayDao, tenantDao) is built with an identical SetLogger/SetTenancyLogic/SetMetricsRegisterer/Build chain. A small generic helper collapses ~70 lines into one function call per DAO.

♻️ Proposed refactor
+func buildDAO[T dao.Object](
+	logger *slog.Logger,
+	tenancyLogic auth.TenancyLogic,
+	registerer prometheus.Registerer,
+) (*dao.GenericDAO[T], error) {
+	return dao.NewGenericDAO[T]().
+		SetLogger(logger).
+		SetTenancyLogic(tenancyLogic).
+		SetMetricsRegisterer(registerer).
+		Build()
+}
+
 func (b *DefaultNetworkingProvisionerBuilder) Build() (result *DefaultNetworkingProvisioner, err error) {
 	...
-	networkClassDao, err := dao.NewGenericDAO[*privatev1.NetworkClass]().
-		SetLogger(b.logger).
-		SetTenancyLogic(b.tenancyLogic).
-		SetMetricsRegisterer(b.metricsRegisterer).
-		Build()
+	networkClassDao, err := buildDAO[*privatev1.NetworkClass](b.logger, b.tenancyLogic, b.metricsRegisterer)
 	if err != nil {
 		return
 	}
 	// ... repeat for the remaining 7 DAOs
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/default_networking_provisioner.go` around lines 69 - 163,
Refactor DefaultNetworkingProvisionerBuilder.Build by introducing a local or
private generic helper that constructs each DAO with the shared logger, tenancy
logic, metrics registerer, and Build chain. Replace the repeated construction
blocks for networkClassDao, virtualNetworkDao, subnetDao, securityGroupDao,
externalIPDao, externalIPPoolDao, natGatewayDao, and tenantDao with helper
calls, preserving existing error propagation and result initialization.

- Add owner-reference annotation on NATGateway pointing to VN ID,
  consistent with Subnet and SecurityGroup
- Remove internal error details from gRPC response (already logged
  via ErrorContext)

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Dan Manor <dmanor@redhat.com>
@danmanor danmanor changed the title WIP: OSAC-2341: create default networking resources at tenant onboarding OSAC-2341: create default networking resources at tenant onboarding Jul 29, 2026

@ori-amizur ori-amizur left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review comments on a few spots — mostly clarification questions and one potential gap.

VirtualNetwork: vnID,
Ingress: defaults.GetIngressRules(),
Egress: defaults.GetEgressRules(),
ImplementationStrategy: "network_policy",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is "network_policy" intentionally hardcoded here? The VirtualNetwork correctly copies implementation_strategy from the NetworkClass, but the SecurityGroup uses a static value. Same question about Region: "default" on the VN (line 246) — should these come from the NetworkClass or deployment config, or are they always fixed?

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.

Both are intentional:

  • "network_policy" — matches the existing pattern in private_security_groups_server.go:31 (const securityGroupImplementationStrategy = "network_policy"). The SG server always hardcodes this on Create (line 161). SGs are enforced via Kubernetes NetworkPolicy, not via the fabric manager.

  • "default" region — matches the public VN server behavior when region is unset (virtual_networks_server.go:244-245). Region is a private-API field; the public server always sets it to "default".

}

func (p *DefaultNetworkingProvisioner) findExternalIPPool(ctx context.Context) (*privatev1.ExternalIPPool, error) {
listResponse, err := p.externalIPPoolDao.List().Do(ctx)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

findExternalIPPool lists all pools with no tenant filter. In a multi-tenant deployment, this would return pools from every tenant. Should it be scoped to system-level pools or pools available to the target tenant?

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.

ExternalIPPools are platform-level resources, not tenant-scoped. The existing private_external_ips_server.go also does unscoped pool lookups via validatePoolReference (line 275) — pools are managed by Cloud Provider Admins and shared across tenants. The DAO List() here follows the same pattern.


pool := getResponse.GetObject()
pool.GetStatus().SetAllocated(pool.GetStatus().GetAllocated() + delta)
pool.GetStatus().SetAvailable(pool.GetStatus().GetAvailable() - delta)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

After the locked Get, there is no check that available >= delta before decrementing. The available > 0 filter in findExternalIPPool runs before the lock is acquired, so by the time the row is locked and re-read here, the capacity could have changed. A guard like:

if pool.GetStatus().GetAvailable() < delta {
    return fmt.Errorf("ExternalIP pool %s has insufficient capacity", poolID)
}

after the locked read would close this gap.

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.

Good catch — fixed in commit 90fb398. Added newAvailable < 0 guard after the locked read, matching the existing pattern in private_external_ips_server.go:325.

Check newAvailable >= 0 after acquiring the row lock in
updatePoolCapacity, matching the existing pattern in
private_external_ips_server.go. Closes the TOCTOU gap between
the unlocked findExternalIPPool check and the locked capacity update.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Dan Manor <dmanor@redhat.com>

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
internal/servers/default_networking_provisioner.go (1)

466-473: ⚠️ Potential issue | 🟠 Major

Guard missing pool status before mutation.

pool.GetStatus() can be nil; the SetAllocated/SetAvailable calls then panic even though the capacity check is otherwise correct. Return an error before reading or mutating the status.

🛡️ Proposed fix
 	pool := getResponse.GetObject()
+	if !pool.HasStatus() {
+		return fmt.Errorf("ExternalIP pool '%s' has no status", poolID)
+	}
 	newAllocated := pool.GetStatus().GetAllocated() + delta
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/default_networking_provisioner.go` around lines 466 - 473,
In the pool status update logic, validate that pool.GetStatus() is non-nil
before calculating newAllocated/newAvailable or mutating it. Return an
appropriate error when status is missing, while preserving the existing capacity
check and updates for valid statuses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/servers/default_networking_provisioner_test.go`:
- Around line 324-334: Extend the NAT gateway assertions in the test around ng
and vnID to verify ng.GetSpec().GetVirtualNetwork() equals vnID, preserving the
existing owner-reference and other state checks.

---

Duplicate comments:
In `@internal/servers/default_networking_provisioner.go`:
- Around line 466-473: In the pool status update logic, validate that
pool.GetStatus() is non-nil before calculating newAllocated/newAvailable or
mutating it. Return an appropriate error when status is missing, while
preserving the existing capacity check and updates for valid statuses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 799727e9-0dbd-4cc7-9c7a-1c5c3da62f86

📥 Commits

Reviewing files that changed from the base of the PR and between 83c94a9 and 90fb398.

📒 Files selected for processing (3)
  • internal/servers/default_networking_provisioner.go
  • internal/servers/default_networking_provisioner_test.go
  • internal/servers/private_tenants_server.go

Comment on lines +324 to +334
vnList, err := provisioner.virtualNetworkDao.List().
SetFilter("this.metadata.tenant == 'nat-tenant'").
Do(ctx)
Expect(err).ToNot(HaveOccurred())
vnID := vnList.GetItems()[0].GetId()

ng := ngList.GetItems()[0]
Expect(ng.GetMetadata().GetLabels()).To(HaveKeyWithValue("osac.openshift.io/default", "true"))
Expect(ng.GetMetadata().GetAnnotations()).To(HaveKeyWithValue("osac.openshift.io/owner-reference", vnID))
Expect(ng.GetSpec().GetExternalIp()).To(Equal(eip.GetId()))
Expect(ng.GetStatus().GetState()).To(Equal(privatev1.NATGatewayState_NAT_GATEWAY_STATE_PENDING))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the NATGateway’s VirtualNetwork link too.

The test captures vnID but only verifies the owner-reference annotation. Add an assertion for ng.GetSpec().GetVirtualNetwork() so a regression can’t leave the NATGateway associated with the wrong or empty VirtualNetwork.

🧪 Proposed assertion
 			ng := ngList.GetItems()[0]
 			Expect(ng.GetMetadata().GetLabels()).To(HaveKeyWithValue("osac.openshift.io/default", "true"))
 			Expect(ng.GetMetadata().GetAnnotations()).To(HaveKeyWithValue("osac.openshift.io/owner-reference", vnID))
+			Expect(ng.GetSpec().GetVirtualNetwork()).To(Equal(vnID))
 			Expect(ng.GetSpec().GetExternalIp()).To(Equal(eip.GetId()))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vnList, err := provisioner.virtualNetworkDao.List().
SetFilter("this.metadata.tenant == 'nat-tenant'").
Do(ctx)
Expect(err).ToNot(HaveOccurred())
vnID := vnList.GetItems()[0].GetId()
ng := ngList.GetItems()[0]
Expect(ng.GetMetadata().GetLabels()).To(HaveKeyWithValue("osac.openshift.io/default", "true"))
Expect(ng.GetMetadata().GetAnnotations()).To(HaveKeyWithValue("osac.openshift.io/owner-reference", vnID))
Expect(ng.GetSpec().GetExternalIp()).To(Equal(eip.GetId()))
Expect(ng.GetStatus().GetState()).To(Equal(privatev1.NATGatewayState_NAT_GATEWAY_STATE_PENDING))
vnList, err := provisioner.virtualNetworkDao.List().
SetFilter("this.metadata.tenant == 'nat-tenant'").
Do(ctx)
Expect(err).ToNot(HaveOccurred())
vnID := vnList.GetItems()[0].GetId()
ng := ngList.GetItems()[0]
Expect(ng.GetMetadata().GetLabels()).To(HaveKeyWithValue("osac.openshift.io/default", "true"))
Expect(ng.GetMetadata().GetAnnotations()).To(HaveKeyWithValue("osac.openshift.io/owner-reference", vnID))
Expect(ng.GetSpec().GetVirtualNetwork()).To(Equal(vnID))
Expect(ng.GetSpec().GetExternalIp()).To(Equal(eip.GetId()))
Expect(ng.GetStatus().GetState()).To(Equal(privatev1.NATGatewayState_NAT_GATEWAY_STATE_PENDING))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/servers/default_networking_provisioner_test.go` around lines 324 -
334, Extend the NAT gateway assertions in the test around ng and vnID to verify
ng.GetSpec().GetVirtualNetwork() equals vnID, preserving the existing
owner-reference and other state checks.

@danmanor

Copy link
Copy Markdown
Contributor Author

/override "E2E BMaaS Full Install / e2e"

@openshift-ci

openshift-ci Bot commented Jul 29, 2026

Copy link
Copy Markdown

@danmanor: /override requires failed status contexts, check run or a prowjob name to operate on.
The following unknown contexts/checkruns were given:

  • E2E BMaaS Full Install / e2e

Only the following failed contexts/checkruns were expected:

  • CodeRabbit
  • e2e-bmaas-full-install / e2e
  • remove-label
  • tide

If you are trying to override a checkrun that has a space in it, you must put a double quote on the context.

Details

In response to this:

/override "E2E BMaaS Full Install / e2e"

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.

@danmanor

Copy link
Copy Markdown
Contributor Author

/override e2e-bmaas-full-install / e2e

@openshift-ci

openshift-ci Bot commented Jul 29, 2026

Copy link
Copy Markdown

@danmanor: /override requires failed status contexts, check run or a prowjob name to operate on.
The following unknown contexts/checkruns were given:

  • /
  • e2e
  • e2e-bmaas-full-install

Only the following failed contexts/checkruns were expected:

  • CodeRabbit
  • e2e-bmaas-full-install / e2e
  • remove-label
  • tide

If you are trying to override a checkrun that has a space in it, you must put a double quote on the context.

Details

In response to this:

/override e2e-bmaas-full-install / e2e

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.

@danmanor

Copy link
Copy Markdown
Contributor Author

/override "e2e-bmaas-full-install / e2e"

@openshift-ci

openshift-ci Bot commented Jul 29, 2026

Copy link
Copy Markdown

@danmanor: Overrode contexts on behalf of danmanor: e2e-bmaas-full-install / e2e

Details

In response to this:

/override "e2e-bmaas-full-install / e2e"

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.

@ori-amizur

Copy link
Copy Markdown
Contributor

/lgtm

@openshift-ci openshift-ci Bot added the lgtm label Jul 29, 2026
@openshift-merge-bot
openshift-merge-bot Bot merged commit e20fdfc into osac-project:main Jul 29, 2026
16 of 18 checks passed

This branch was previously deployed

1 inactive deployment
e2e-test — 90fb3984 Deployed Jul 29, 2026 by danmanor via e2e-vmaas-full-install / e2e #818
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants