Skip to content

Add a simulated dev/test environment and simulator-based e2e CI - #105

Open
vishesh92 wants to merge 1 commit into
mainfrom
add-simulator-e2e-environment
Open

Add a simulated dev/test environment and simulator-based e2e CI#105
vishesh92 wants to merge 1 commit into
mainfrom
add-simulator-e2e-environment

Conversation

@vishesh92

@vishesh92 vishesh92 commented Aug 31, 2026

Copy link
Copy Markdown
Member

Fixes #4

What this does

Issue #4 asked for documentation on running kube-apiserver and
cloudstack-simulator locally, "preferring containers so automated test
workloads can be implemented later." This does both halves.

make e2e-up brings up a complete, unmocked stack: a kind cluster, the
CloudStack simulator with its advanced zone deployed, CloudStack VMs matching
the kind nodes, and the CCM built from the checkout. docs/development.md
walks through the same steps manually, so the environment can be understood and
debugged rather than just invoked.

make e2e-up
make test-e2e        # phase 1: load balancer, nodes, annotations
make e2e-vpc         # switch to a VPC in a project
make test-e2e-vpc    # phase 2: VPC / network ACL
make e2e-down

Why it is worth the files

EnsureLoadBalancer, UpdateLoadBalancer and EnsureLoadBalancerDeleted had
no tests. Only their helpers did, and those mostly pass gomock.Any() for
request parameters, so what the CCM actually sends to CloudStack was largely
unverified. The new suite is 17 tests (13 phase 1, 4 VPC phase) covering load
balancer lifecycle, node initialization, annotations and session affinity, and
the VPC/network ACL path against a real management server. It lives in
test/e2e behind the e2e build tag, so it stays out of make test and
go build ./..., and needs no new module dependencies.

Bugs this found and fixes

All CCM-side, all exposed by running the code end to end:

  • Project scoping (three call sites). updateNetworkACL,
    getNetworkIDFromIPAddress, and the disassociation check in
    EnsureLoadBalancerDeleted fetched CloudStack resources without the
    configured project. On a VPC owned by a project, every LoadBalancer service
    failed with error fetching Network with ID and never received an ingress
    address, and the public IP leaked on deletion.
  • Wrong error variable / field mismatch in getNetworkIDFromIPAddress: a
    failed network lookup was reported as success, and the guard checked
    Networkid while the lookup used Associatednetworkid. Either could hand the
    caller an empty network ID, which GetNetworkByID resolves as an unfiltered
    network list rather than rejecting.
  • Load balancer rule leak. CloudStack does not enforce unique rule names,
    but loadBalancer.rules is keyed by name, so a duplicate displaced its twin
    in the map and survived EnsureLoadBalancerDeleted with no service left to
    reference it. Duplicates are now tracked and removed on both reconcile and
    delete, without disturbing the rule that is kept.
  • Two startup/response panics hardened: getManagementServerVersion sliced
    the version string to three components without a length check (crashing the
    controller on a short version), and getPublicIPAddress guarded on the result
    count but indexed the slice.

Each fix has unit tests; the project-scoping and rule-leak fixes are also
covered end to end by the VPC phase.

CI

.github/workflows/e2e-simulator.yml runs on pull requests and pushes to
main, as a 2x2 matrix:

Kubernetes CloudStack
v1.37.0, v1.36.4 4.22.1.0, 4.20.2.0

The CloudStack axis buys branch coverage, not just version coverage: 4.22+
updates a rule's CIDR list in place, 4.20 deletes and recreates it. A shared
build job compiles the image once and the four cells run in parallel; the
simulator and kind node images are cached between runs, so the workflow costs
about as much wall-clock as a single run. Failures upload simulator logs, CCM
logs, node and service dumps, and CloudStack-side state.

Test invocation goes through the make targets rather than being duplicated in
the workflow, so CI and a local run cannot drift apart. Not added as a required
check in .asf.yaml yet — worth letting it prove itself stable first.

Known limitation

kind starts kubelet with --provider-id=kind://..., and Kubernetes only lets a
provider ID be set once, so the CCM never assigns external-cloudstack://<uuid>
in this environment. TestNode_ProviderID detects that, logs the value it would
have assigned, and reports itself as skipped, so the gap stays visible instead
of quietly passing. Documented in docs/development.md.

Notes for reviewers

  • The README's Debugging section moved to docs/development.md, and the
    stale cloudstack/simulator Docker Hub link is corrected to
    apache/cloudstack-simulator.
  • .gitignore now covers the local cloud-config, cmk-config and
    kube-config files, which hold live credentials and were previously
    untracked but not ignored.
  • A handful of untested behaviours are deliberately left for follow-up PRs:
    UpdateLoadBalancer node add/remove, the proxy-protocol and UDP/TCP-Proxy
    paths, spec.loadBalancerSourceRanges, and VPC + Firewall (the tier offering
    advertises NetworkACL only).

Testing

Ran the full harness locally against apache/cloudstack-simulator:

  • phase 1: 12 pass, 1 informative skip (provider ID, above), ~100s
  • phase 2 (VPC): 4 pass, ~30s; the project-scoping and rule-leak fixes were
    confirmed by reproducing each bug against the live simulator before the fix
  • make test, golangci-lint run, go vet -tags e2e and go build ./... are
    clean

The CCM's k8s.io/* v0.24 libraries worked fine against a v1.37 API server.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings August 31, 2026 08:39

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov-commenter

codecov-commenter commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.64%. Comparing base (151452f) to head (13692b4).

Files with missing lines Patch % Lines
cloudstack_loadbalancer.go 63.15% 12 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #105      +/-   ##
==========================================
+ Coverage   50.05%   50.64%   +0.59%     
==========================================
  Files           4        4              
  Lines         975     1001      +26     
==========================================
+ Hits          488      507      +19     
- Misses        473      479       +6     
- Partials       14       15       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vishesh92
vishesh92 force-pushed the add-simulator-e2e-environment branch from 9e0a52b to b3a11f1 Compare August 31, 2026 10:19
Copilot AI review requested due to automatic review settings August 31, 2026 10:19

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vishesh92
vishesh92 force-pushed the add-simulator-e2e-environment branch from b3a11f1 to 193dabc Compare September 1, 2026 04:54
Copilot AI review requested due to automatic review settings September 1, 2026 04:54

Copilot AI 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.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 4 comments.

Suppressed comments (1)

cloudstack_loadbalancer.go:487

  • The netErr branch returns the wrong error variable (it returns err from the previous GetPublicIpAddressByID call). If the first call succeeded (err==nil) but GetNetworkByID fails, this currently returns a nil error and masks the failure.
		network, _, netErr := cs.client.Network.GetNetworkByID(ip.Associatednetworkid, cloudstack.WithProject(cs.projectID))
		if netErr != nil {
			klog.Errorf("Failed to fetch the network for id: %v", ip.Associatednetworkid)
			return "", err
		}

Comment thread cloudstack_loadbalancer.go
Comment thread test/e2e/framework.go
Comment thread test/e2e/vpc_test.go Outdated
Comment thread .github/workflows/e2e-simulator.yml
@vishesh92
vishesh92 force-pushed the add-simulator-e2e-environment branch from 193dabc to f656173 Compare September 1, 2026 07:26
Copilot AI review requested due to automatic review settings September 1, 2026 07:26

Copilot AI 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.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 2 comments.

Comment thread hack/e2e/10-simulator-up.sh Outdated
Comment thread test/e2e/framework.go Outdated
@vishesh92
vishesh92 force-pushed the add-simulator-e2e-environment branch from f656173 to 98f7d4a Compare September 1, 2026 09:22
Copilot AI review requested due to automatic review settings September 1, 2026 09:22

Copilot AI 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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 5 comments.

Comment thread Makefile Outdated
Comment thread test/e2e/annotations_test.go Outdated
Comment thread test/e2e/loadbalancer_test.go
Comment thread test/e2e/framework.go Outdated
Comment thread test/e2e/framework.go
Copilot AI review requested due to automatic review settings September 1, 2026 09:59
@vishesh92
vishesh92 force-pushed the add-simulator-e2e-environment branch from 98f7d4a to 0ac220a Compare September 1, 2026 09:59

Copilot AI 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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 26 out of 27 changed files in this pull request and generated 6 comments.

Comment thread test/e2e/framework.go Outdated
Comment thread test/e2e/annotations_test.go
Comment thread cloudstack_loadbalancer.go
Comment thread cloudstack_loadbalancer.go
Comment thread Makefile
Comment thread Makefile
Copilot AI review requested due to automatic review settings September 1, 2026 10:59
@vishesh92
vishesh92 force-pushed the add-simulator-e2e-environment branch from 0ac220a to 80f3681 Compare September 1, 2026 10:59

Copilot AI 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.

Pull request overview

Copilot reviewed 26 out of 27 changed files in this pull request and generated 2 comments.

Comment thread test/e2e/framework.go
Comment thread hack/e2e/90-collect-artifacts.sh
Copilot AI review requested due to automatic review settings September 1, 2026 11:23
@vishesh92
vishesh92 force-pushed the add-simulator-e2e-environment branch from 80f3681 to f5f9f69 Compare September 1, 2026 11:23

Copilot AI 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.

Pull request overview

Copilot reviewed 26 out of 27 changed files in this pull request and generated no new comments.

The repository had no way to exercise the CCM end to end. The only
"run against real CloudStack" hook was configFromEnv() in
cloudstack_test.go, which skips unless CS_API_URL and friends are set,
and nothing set them. As a result EnsureLoadBalancer,
UpdateLoadBalancer and EnsureLoadBalancerDeleted -- the three functions
holding nearly all of the load balancer branching -- had no test
coverage at all, and the README pointed at a Docker Hub image
(cloudstack/simulator) that no longer exists.

Add hack/e2e, which brings up a CloudStack simulator, deploys its
advanced zone, mints admin API keys, creates a kind cluster and
deploys CloudStack VMs matching its nodes, then runs the CCM against
both. CloudStack calls go through cmk, so the scripts run the same
commands the documentation tells you to run, and cmk's own async job
handling removes any need to poll queryAsyncJobResult.
docs/development.md walks through the same steps by hand so the
environment is understandable rather than magic.

Add a Go e2e suite under test/e2e covering load balancer lifecycle,
node initialization, service annotations and the VPC/network ACL path.
It is behind the e2e build tag, so it stays out of `make test` and
`go build ./...`, and it needs no new module dependencies.

Run all of it in CI as a matrix of the latest two Kubernetes minors
against CloudStack 4.22.1.0 and 4.20.2.0. The CloudStack axis is not
only version coverage: 4.22 and later update a load balancer rule's
CIDR list in place while earlier releases delete and recreate the
rule, so both branches are exercised. Cells run in parallel and share
a single image build, and the simulator and kind node images are
cached between runs, so the workflow costs about as much wall-clock
as a single run.

Fix several latent bugs in the load balancer path that the new suite
exposed. Three call sites fetched CloudStack resources without the
configured project: updateNetworkACL (the network and its ACL list),
getNetworkIDFromIPAddress (the public IP and its network), and the
disassociation check in EnsureLoadBalancerDeleted. On a VPC owned by a
project this made every LoadBalancer service fail with "error fetching
Network with ID" and never get an ingress address, and it leaked the
public IP on deletion. getNetworkIDFromIPAddress also reported a failed
network lookup as success by returning the wrong error variable, and
guarded on Networkid while looking up Associatednetworkid; either could
hand the caller an empty network ID, which GetNetworkByID does not
reject but looks up as an unfiltered network list, so it could resolve
to an arbitrary network instead of failing.

Fix a load balancer rule leak. CloudStack does not enforce unique rule
names, but loadBalancer.rules is keyed by name, so a duplicate silently
displaced its twin in the map and then survived EnsureLoadBalancerDeleted
with no service left to reference it. Duplicates are now tracked
separately and removed on both reconcile and delete, without disturbing
the rule that is kept.

Harden two paths that panicked on an unexpected management-server
response: getManagementServerVersion sliced the version string to three
components without checking its length, crashing the controller at
startup on a short version, and getPublicIPAddress guarded on the result
count but indexed the slice, which would panic if the two disagreed.

Also add the local cloud-config, cmk-config and kube-config files to
.gitignore. They hold live credentials and were previously untracked
but not ignored.

Fixes #4

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vishesh92
vishesh92 force-pushed the add-simulator-e2e-environment branch from f5f9f69 to 13692b4 Compare September 2, 2026 07:09
Copilot AI review requested due to automatic review settings September 2, 2026 07:09

Copilot AI 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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 26 out of 27 changed files in this pull request and generated 2 comments.

Comment thread hack/e2e/90-collect-artifacts.sh
Comment thread test/e2e/framework.go
@vishesh92
vishesh92 marked this pull request as ready for review September 2, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[OLD] Document how to run a fully simulated dev/test environment

3 participants