Add OCI Compute and Block Volume: instances, volumes, images, pools - #426
Add OCI Compute and Block Volume: instances, volumes, images, pools#426arunesh-j wants to merge 2 commits into
Conversation
NitinKumar004
left a comment
There was a problem hiding this comment.
Review notes
Real data-plane engine (per #427): MISSING. See finding below.
CI: CodeQL fails — high go/uncontrolled-allocation-size at providers/oci/compute/instance.go:67. Guard the size before allocating.
Findings
Medium · real-engine — OCI Compute does not route its data plane through the shared ComputeEngine helper (#427 divergence)
providers/oci/compute/instance.go:60
If a user does cloudemu.NewOCI(config.WithComputeEngine(dockerEngine)) then LaunchInstance -> no real backing is provisioned and the instance keeps its synthetic 10.0.x private IP, because RunInstances never invokes computeengine.Provision — the opt-in real engine silently works for AWS/Azure/GCP compute but is a no-op for OCI, breaking cross-cloud parity.
RunInstances (instance.go:60) and TerminateInstance (instance.go:214) never call services/compute/computeengine.Provision/Deprovision, and the mock never reads o.ComputeEngine. AWS ec2 (providers/aws/ec2/ec2.go:301,341,452) wires all three hooks. #427 is merged on development; this PR branched from f1bc63d which predates it, so it compiles+merges green without the wiring.
Low · structure — Service branch modifies the shared topology engine beyond the three-slot scope
features/topology/connectivity.go:152
If an AWS/Azure/GCP instance references a security-group id the provider mock no longer returns (e.g. a deleted SG) -> topology now issues an extra DescribeNetworkACLs lookup for that id where before it was ignored; benign today because sg/acl id namespaces are disjoint so the lookup returns nothing, but it widens an OCI branch's blast radius into an engine every provider's connectivity path depends on, with no non-OCI regression test.
findMatchingSGRule now falls through to findMatchingACLRule for any instance security id the VCN/VPC mock's DescribeSecurityGroups did not return (connectivity.go:186 unresolved()). This is a shared cross-service engine used by CanConnect for ALL providers, changed in an OCI service branch and tested only via features/topology/oci_security_list_test.go. Conventions say a service branch touches only the three slot files + providers/oci/ + server/oci/, and not to modify the shared founda
Low · correctness — RunInstances count>1 has no rollback on a mid-loop failure
providers/oci/compute/instance.go:69
If a user calls RunInstances with count>1 and placement fails after the first instance is created -> the caller gets an error but the already-created instances are orphaned in the mock. Low impact in practice: all instances share one InstanceConfig, so a placement error (bad/absent subnet) fails on the first iteration and creates nothing; only a transient VCN error (which the mock does not generate) could partially create.
The launch loop (instance.go:69-81) creates each instance's VNIC (place) then stores it; on an error from place() for a later iteration it returns nil,err while earlier instances and their VNICs/boot volumes remain in the stores.
NitinKumar004
left a comment
There was a problem hiding this comment.
Review — OCI Compute + Block Volume
Verdict: Request changes (merge-after-rebase). The compute/state-machine substance is strong, but the PR can't merge as-is: CI is red, mergeable: CONFLICTING, and the merged tree neither compiles nor passes the persistence gate.
On the red CI
The single red check is CodeQL, not any Go gate (Build/Vet/Test/Lint/Structure/Tidy/Format all pass):
- CodeQL
go/uncontrolled-allocation-size—providers/oci/compute/instance.go:67(make([]driver.Instance, 0, count)).countis bounded —validateLaunchrejectscount > maxLaunchCount (1000)earlier in the flow — so this is an inter-procedural false-positive CodeQL can't see across the function boundary. It still reddens CI and blocks merge. Fix: make the bound visible to data-flow (clamp themakecapacity, or inline thecount > maxLaunchCountcheck immediately before themake), or dismiss the alert.
Blocking (surface on rebase onto current development)
- Merged tree does not compile — stale driver signature —
providers/oci/compute/volume.go:259.developmentevolvedcompute/driver.Compute.DetachVolumeto(ctx, volumeID, instanceID, device string); this PR implements the old 2-argDetachVolume(ctx, volumeID), so*compute.Mockno longer satisfiesdriver.Compute(thecompute.go:70compile-assert fails). Fix: update to the 3-arg signature. - Missing
snapshot.go— persist completeness gate fails —providers/oci/compute/(no snapshot.go).*compute.Mockholds 16memstore.Storefields but implements nosnapshot.Snapshottable, while siblingsvcn/identity/monitoringall shipsnapshot.go. On the merged treeTestSnapshotCompleteness/ocierrors *"field Compute (compute.Mock) holds a memstore.Store but is not Snapshottable". The PR's own CI was green only because it branched before that guard landed. Fix: addsnapshot.gocovering all 16 stores (instances, details, images, volumes, volAttach, bootVolumes, bootAttach, backups, volGroups, vnicAttach, pools, configs, spot, shapes, scopes, created). Consequence: a snapshot/restore silently drops all OCI compute + block-volume state. - Merge conflicts —
providers/oci/oci.go,features/topology/connectivity.go, generateddocs/coverage/*. oci.go and docs are mechanical, butconnectivity.gois a real code overlap: development refactored the samematchRulesregion (value-loop → pointer-loop, newtargetIPparam) while this PR added afoundmap for OCI security-list connectivity — reconcile by hand.
Should fix
- STOP on an already-STOPPED instance returns
200idempotent —providers/oci/compute/instance.go(StopInstances). Real OCIInstanceAction STOPon a STOPPED instance returns409 IncorrectState. Defensible EC2-style choice, but a genuine OCI deviation. - Error
messageleaks the internal cerrors prefix — double-attach returnedmessage: "AlreadyExists: volume ... is already attached"; theAlreadyExists:prefix is CloudEmu-internal (real OCI omits it). Thecode("Conflict") is correct. Sharedocirestbehavior — cosmetic.
Non-blocking
- Batch
applyTransitionpartial application without rollback —instance.go. In a multi-ID batch, IDs processed before a failing one stay mutated (metrics aren't emitted, limiting impact). Matches AWS EC2 batch semantics; minor.
Verified good
State machine correct (§19): Start(Stopped→Running), Stop(Running→Stopped), Reset, idempotent + FailedPrecondition on illegal states; terminated instances → 404 on get/action; volume attach rejects double-attach, detach rejects unattached. Live e2e over the wire: VCN→subnet→launch(RUNNING)→STOP→START→GET(stable id/timeCreated)→volume(AVAILABLE)→attach(ATTACHED)→attach-again(409)→detach(204)→detach-again(404)→terminate(204)→get-terminated(404). Pool/launch sizes bounded (maxLaunchCount/maxPoolSize=1000, enforced through the scaling-policy path too — no unbounded alloc). Reads deep-copy; creates mint fresh OCIDs (no check-then-set race); -race clean. Wiring complete (from_provider.go, handler on VCN's shared prefix with work-request registry, auto-metrics on launch/lifecycle, monitoring wired); naming conformant.
Path to merge
- Resolve the CodeQL alloc alert (make the bound data-flow-visible).
- Rebase onto
development; fixDetachVolumeto the 3-arg signature. - Add
providers/oci/compute/snapshot.go(+ test) for all 16 stores. - Hand-merge the
connectivity.go/oci.go/ docs conflicts.
Implement the portable compute driver for OCI over an in-memory mock, and serve it on OCI's /20160918 Core Compute and Block Volume surface: instances (launch/get/list/update/terminate and the START, STOP, SOFTSTOP, RESET and SOFTRESET actions), shapes, images, boot volumes, block volumes, their attachments, volume backups, volume groups, VNIC attachments, and instance configurations and pools as the launch-template and autoscaling equivalent. Networking comes from VCN: launching an instance creates a VNIC in a subnet through providers/oci/vcn's NetworkInterfaceCreator rather than modeling one here. Instance pools are the auto-scaling group, instance configurations the launch template, volume backups the snapshot, and preemptible instances spot. OCI models no key pair resource, so those driver calls report the gap and name the ssh_authorized_keys instance metadata entry instead of inventing one. OCI-only behaviour is a consumer-side server/oci/compute.Extras with its value types in providers/oci/compute; nothing was added to services/compute/driver, so no OCI operation reaches the AWS, Azure or GCP coverage pages. The mutations real OCI runs asynchronously record a work request and stamp opc-work-request-id. Close the connectivity gap deferred from the VCN review: features/topology's CanConnect evaluated network security groups only, so an OCI instance governed by its subnet's security list read as unreachable. An instance's attached rule collections are now resolved as security groups and, failing that, as network ACLs — the union real OCI applies — and the OCI mock records both on the instance at launch. Route the data plane through the shared ComputeEngine helper so an opt-in real engine backs an OCI instance as it does an AWS, Azure or GCP one: RunInstances provisions, TerminateInstance deprovisions, and the mock serves the optional ConsoleReader capability. A launch that fails part-way now rolls back the instances, VNICs and boot volumes it already created rather than orphaning them, and the per-call count is bounded where the bound is visible to the allocation it guards.
…semantics Add providers/oci/compute/snapshot.go so the mock satisfies snapshot.Snapshottable and a snapshot/restore no longer silently drops every instance, volume and pool: all sixteen stores round-trip keyed by OCID, so the cross-references between them still resolve. Two of the values needed work to survive JSON — an instance's engine-backed flag is now exported, and a pool's scaling policies live in a nested store, which it marshals beside its plain fields and rebuilds on restore rather than coming back nil. The synthetic private-address counter is carried too, so a restore does not re-issue addresses already in use. STOP on an already-stopped instance answered 200; real OCI answers 409 IncorrectState, and it now does too. START stays the documented no-op, and a pool action still skips members already in the target state rather than failing the whole pool. A launch left its VNIC attachment, boot volume and boot-volume attachment in the mock's default compartment, so ListVnicAttachments in the compartment that launched the instance came back empty and the instance's own network interface was unreachable over the API. They are now placed with the instance. Shrinking an instance pool read the instance to terminate under a read lock and released it before the terminate, as the cross-service discipline requires, so two concurrent resizes could pick the same instance and the loser failed the whole resize with NotFound. A vanished instance is the shrink that iteration wanted, not an error.
872628e to
2d02923
Compare
|
Rebased onto 1. CodeQL — you were right that it's a false positive
2.
|
| # | Step | Result |
|---|---|---|
| 4 | LaunchInstance | RUNNING + Opc-Work-Request-Id |
| 6 | ListVnicAttachments | 1 ATTACHED in the caller's compartment (was [] before the fix) |
| 8/9 | AttachVolume, again | 200 ATTACHED, then 409 Conflict, message clean |
| 10/11 | STOP, STOP again | 200 STOPPED, then 409 IncorrectState |
| 12 | ListWorkRequests | 4 SUCCEEDED |
| 13/14 | Terminate, Get | 204, then 404 |
Summary
computedriver.CanConnectnow evaluates OCI security lists, not just NSGs.services/compute/driver; OCI-only behaviour is a consumer-sideExtrasinterface, per Move OCI-only capabilities out of shared driver packages #393.Closes #411. Part of #376. Depends on OCI VCN (#379), merged.
Changes
providers/oci/compute/—Mockovermemstoreimplementingdriver.Compute, singlesync.RWMutex.server/oci/compute/— the/20160918/Core Compute surface.features/topology/— the deferred security-list fix (see below).providers/oci/oci.goandserver/oci/oci.go.Operations: instances (launch/get/list/update/terminate,
?action=START|STOP|SOFTSTOP|RESET|SOFTRESET), shapes, images, volumes and attachments, boot volumes and attachments, volume and boot-volume backups, volume groups, VNIC attachments, instance configurations (+actions/launch), instance pools (+ actions and/instances), andchangeCompartmenton every collection. Preemptible → spot, pools → auto-scaling groups, configurations → launch templates, backups → snapshots.The deferred topology gap
CanConnectreached onlyDescribeSecurityGroups, so an OCI instance with no NSG false-DENYed. Fixed in two halves:providers/oci/computerecords an instance's effective rule collections at launch — its VNIC's NSGs plus its subnet's security lists (falling back to the VCN's default list).features/topology'sfindMatchingSGRulenow resolves each attached id as a security group and, for ids that are not, as a network ACL (allow rules only) — the union real OCI applies.AWS is provably unaffected: its instances carry only security groups, so every id resolves and the ACL branch is never reached. The only other edit in that package is extracting
"allow"into anactionAllowconstant. All existing topology tests pass unchanged.New tests:
TestCanConnectHonoursOCISecurityLists(SSH allowed by the default list, HTTP denied),TestCanConnectHonoursOCINetworkSecurityGroups(NSG path still wins and is attributed), andTestSubnetSecurityListsReachTheInstancepinning the subnet coupling end-to-end through both handlers.VCN wiring
providers/oci/oci.goholds the concrete*vcn.Mockand callscomputeMock.SetNetworking(vcnMock)— theproviders/aws/aws.goSetSubnetResolverpattern. A narrowNetworkinginterface wraps VCN's existingCreateNetworkInterface,DescribeSubnets,DescribeVNICs,UpdateVNIC,DeleteNetworkInterface,Defaults. A launch creates the VNIC through VCN and records the OCIvnicAttachmentaround VCN's attachment id. No VNIC is modelled in compute.No key pair resource
OCI has none — keys are instance metadata.
CreateKeyPair/DeleteKeyPair/DescribeKeyPairsreturnUnimplementednamingssh_authorized_keys,RunInstancesrejects aKeyNamerather than dropping it, and there is no/keyPairscollection.Matchesis disjoint from VCN'sTested both directions: 16 positive collections, plus explicit negatives for
vcns,subnets,vnics,securityLists,networkSecurityGroups,routeTables,publicIps,workRequestsand other API versions.TestVCNTrafficIsNotSwallowedruns both handlers behind one mux in registration order and confirms VCN still answers.Provider Coverage
Checklist
go test ./...) — exit 0, 272 packagesgolangci-lint run --timeout=9m) — 0 issuescloudemu_test.go— driver + handler + topology tests insteadTest Plan
git diff development -- services/empty; no non-OCI coverage page changed.Lock sweep: every exported
Mockmethod locks, none calls another. The three cross-boundary paths release first —RunInstancescalls into VCN before takingm.mu;syncPoolcomputes its delta underRLock, releases, then launches/terminates;emitMetricsruns after unlock. Nostore.Updateclosure re-enters its store.race_test.godrives 16 goroutines through launch/attach/detach/tag/stop/terminate plus pool resize — it would hang, not merely fail, if that were violated.End-to-end on a running server (port 4612):
One thing worth a follow-up
docs/coverage/oci/compute.mdlistsCreateKeyPair/DeleteKeyPair/DescribeKeyPairseven though OCI returnsUnimplementedfor them, becausecoveragegenrenders every driver method for every provider implementing the service. I deliberately did not add adocs/coverage/nongoals/compute.mdfragment: those are per-service, so the note would also render on the AWS, Azure and GCP compute pages, which do implement key pairs.The gap is documented in
docs/services.mdand in the driver instead. Making the coverage doc accurate needs a per-provider non-goals hook incoveragegen— same family as #394, and worth its own issue.