Add a simulated dev/test environment and simulator-based e2e CI - #105
Add a simulated dev/test environment and simulator-based e2e CI#105vishesh92 wants to merge 1 commit into
Conversation
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
9e0a52b to
b3a11f1
Compare
b3a11f1 to
193dabc
Compare
There was a problem hiding this comment.
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
}
193dabc to
f656173
Compare
f656173 to
98f7d4a
Compare
There was a problem hiding this comment.
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.
98f7d4a to
0ac220a
Compare
There was a problem hiding this comment.
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.
0ac220a to
80f3681
Compare
80f3681 to
f5f9f69
Compare
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>
f5f9f69 to
13692b4
Compare
There was a problem hiding this comment.
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.
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-upbrings up a complete, unmocked stack: a kind cluster, theCloudStack simulator with its advanced zone deployed, CloudStack VMs matching
the kind nodes, and the CCM built from the checkout.
docs/development.mdwalks through the same steps manually, so the environment can be understood and
debugged rather than just invoked.
Why it is worth the files
EnsureLoadBalancer,UpdateLoadBalancerandEnsureLoadBalancerDeletedhadno tests. Only their helpers did, and those mostly pass
gomock.Any()forrequest 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/e2ebehind thee2ebuild tag, so it stays out ofmake testandgo build ./..., and needs no new module dependencies.Bugs this found and fixes
All CCM-side, all exposed by running the code end to end:
updateNetworkACL,getNetworkIDFromIPAddress, and the disassociation check inEnsureLoadBalancerDeletedfetched CloudStack resources without theconfigured project. On a VPC owned by a project, every LoadBalancer service
failed with
error fetching Network with IDand never received an ingressaddress, and the public IP leaked on deletion.
getNetworkIDFromIPAddress: afailed network lookup was reported as success, and the guard checked
Networkidwhile the lookup usedAssociatednetworkid. Either could hand thecaller an empty network ID, which
GetNetworkByIDresolves as an unfilterednetwork list rather than rejecting.
but
loadBalancer.rulesis keyed by name, so a duplicate displaced its twinin the map and survived
EnsureLoadBalancerDeletedwith no service left toreference it. Duplicates are now tracked and removed on both reconcile and
delete, without disturbing the rule that is kept.
getManagementServerVersionslicedthe version string to three components without a length check (crashing the
controller on a short version), and
getPublicIPAddressguarded on the resultcount 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.ymlruns on pull requests and pushes tomain, as a 2x2 matrix: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
maketargets rather than being duplicated inthe workflow, so CI and a local run cannot drift apart. Not added as a required
check in
.asf.yamlyet — worth letting it prove itself stable first.Known limitation
kind starts kubelet with
--provider-id=kind://..., and Kubernetes only lets aprovider ID be set once, so the CCM never assigns
external-cloudstack://<uuid>in this environment.
TestNode_ProviderIDdetects that, logs the value it wouldhave assigned, and reports itself as skipped, so the gap stays visible instead
of quietly passing. Documented in
docs/development.md.Notes for reviewers
Debuggingsection moved todocs/development.md, and thestale
cloudstack/simulatorDocker Hub link is corrected toapache/cloudstack-simulator..gitignorenow covers the localcloud-config,cmk-configandkube-configfiles, which hold live credentials and were previouslyuntracked but not ignored.
UpdateLoadBalancernode add/remove, the proxy-protocol and UDP/TCP-Proxypaths,
spec.loadBalancerSourceRanges, and VPC + Firewall (the tier offeringadvertises NetworkACL only).
Testing
Ran the full harness locally against
apache/cloudstack-simulator:confirmed by reproducing each bug against the live simulator before the fix
make test,golangci-lint run,go vet -tags e2eandgo build ./...areclean
The CCM's
k8s.io/*v0.24 libraries worked fine against a v1.37 API server.🤖 Generated with Claude Code