refactor(vpcd): relocate services/vpcd → spinifex/vpcd; drop dead aliases

Phase 4 Slice D (mulga-siv-125.6). Final cleanup of the network-redesign
package layout. services/vpcd/ disappears entirely; the launcher remains
an in-process Service impl, just rehomed under spinifex/vpcd/ (no separate
binary — spx vpcd start stays the cobra entrypoint).

- Delete services/vpcd/mock_ovn.go + ovn.go (compat aliases now dead post
  Slice C; only callsite was launchService's NewLiveOVNClient, inlined to
  ovn.NewLiveClient).
- git-rename services/vpcd/vpcd.go + vpcd_test.go to spinifex/vpcd/.
- Retarget three importers (spinifex/service/service.go +
  service_test.go, cmd/spinifex/cmd/service.go) to the new path.
- Fix stale services/vpcd / TopologyHandler comments in network/reconcile/
  + network/external/types.go + spinifex/vpcd/vpcd.go.
- service-interfaces.yaml vpcd path updated.
- Plan doc §13/§14/§16 + §9.2/§19 updated: drop cmd/vpcd/main.go (was
  speculative; never built) in favour of in-process Service impl at
  spinifex/vpcd/vpcd.go. Plan §16 Created list now lists subscribers/.

Phase 4 plan items 1+2 (remove services/vpcd, entrypoint location)
satisfied. Items 3+4 remain blocked on mulga-siv-125.4 + 125.5 (bundled
join token, plan archive).
This commit is contained in:
joshsiv-mulga
2026-05-25 15:07:59 +10:00
parent c3e2d57209
commit a2a326afc3
14 changed files with 18 additions and 49 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/services/predastore"
"github.com/mulgadc/spinifex/spinifex/services/spinifexui"
"github.com/mulgadc/spinifex/spinifex/services/viperblockd"
"github.com/mulgadc/spinifex/spinifex/services/vpcd"
"github.com/mulgadc/spinifex/spinifex/vpcd"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
+1 -1
View File
@@ -131,7 +131,7 @@ services:
- "s3.*"
vpcd:
path: spinifex/services/vpcd
path: spinifex/vpcd
# OVN-backed VPC / subnet / SG / NAT / IGW data plane.
subscribes:
- "vpc.create"
+2 -3
View File
@@ -6,9 +6,8 @@ package external
// from GwLrpRangeStart..GwLrpRangeEnd (auto-derived from the WAN subnet
// when unset). The upstream-DHCP source model is gone (mulga-siv-125.3.3).
//
// This type duplicates services/vpcd/vpcd.go's ExternalPoolConfig. Dedup is
// out of scope for this bead; the two will merge when L2 (topology) grows
// the L5-driving methods and vpcd's local copy disappears.
// Duplicates spinifex/vpcd.ExternalPoolConfig pending dedup when vpcd's
// Config grows to use this shared type directly.
type ExternalPoolConfig struct {
Name string
RangeStart string
+1 -3
View File
@@ -293,9 +293,7 @@ func (r *reconciler) applyNATGWs(ctx context.Context, intent IntentState, _ Actu
}
// subnetGatewayCIDR returns the .1 host of the subnet's CIDR and the
// prefix bit count. Matches the legacy services/vpcd/topology.go's
// subnetGateway, kept local to the reconcile package so the cross-package
// dependency on services/vpcd can be dropped.
// prefix bit count. Kept local to the reconcile package.
func subnetGatewayCIDR(prefix netip.Prefix) (string, int, error) {
if !prefix.IsValid() {
return "", 0, fmt.Errorf("invalid prefix")
+1 -1
View File
@@ -24,7 +24,7 @@ var DriftInterval = 5 * time.Minute
// missed acquire (another vpcd is leader, or JetStream is unreachable) is
// logged at Debug and the tick is skipped — the next tick retries.
//
// Replaces services/vpcd.ReconcileSGsLoop (30s, SG-only). The 5-minute
// Replaces the legacy ReconcileSGsLoop (30s, SG-only). The 5-minute
// interval matches parent plan §11.4.
func DriftLoop(ctx context.Context, rec Reconciler, nc *nats.Conn, localAZ, holder string) {
js, err := nc.JetStream()
+1 -1
View File
@@ -90,7 +90,7 @@ func matchesLocalAZ(vpcAZ, localAZ string) bool {
}
// keyIsVersion skips the per-bucket version-marker key. Mirrors what every
// other KV consumer in the tree does (services/vpcd/reconcile.go etc).
// other KV consumer in the tree does.
func keyIsVersion(key string) bool { return key == utils.VersionKey }
func loadVPCs(js nats.JetStreamContext, localAZ string, out map[string]topology.VPCSpec) (map[string]struct{}, error) {
+2 -2
View File
@@ -32,8 +32,8 @@ var (
// elected=false so a follow-up retry (systemd Restart=on-failure) can run.
//
// Exported with the same contract as the legacy
// services/vpcd.AcquireReconcileLeader — call sites in services/vpcd/vpcd.go
// switch to this package by changing the import path only.
// AcquireReconcileLeader; spinifex/vpcd/vpcd.go is the sole production
// caller via launchService.
func AcquireLeader(nc *nats.Conn, holder string) (func(), bool) {
js, _ := nc.JetStream()
+2 -2
View File
@@ -8,8 +8,8 @@
// The reconciler replaces the legacy five-pass startup sequence
// (`Reconcile` + `ReconcileFromKV` + `ReconcileSGsOnce` +
// `RetrofitAllExternalLocalnetOptions` + `RetrofitAllGatewayPortNetworks`)
// in `services/vpcd`. A 5-minute drift goroutine drives the same call
// periodically, replacing the 30-second `ReconcileSGsLoop`.
// the vpcd daemon ran on boot. A 5-minute drift goroutine drives the same
// call periodically, replacing the 30-second `ReconcileSGsLoop`.
//
// Multi-AZ scoping: `IntentState` is built by `LoadIntentFromKV(ctx, js,
// localAZ)`. The filter rule is `vpc.AZ == "" || vpc.AZ == localAZ` —
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/services/spinifex"
"github.com/mulgadc/spinifex/spinifex/services/spinifexui"
"github.com/mulgadc/spinifex/spinifex/services/viperblockd"
"github.com/mulgadc/spinifex/spinifex/services/vpcd"
"github.com/mulgadc/spinifex/spinifex/vpcd"
)
type Service interface {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/services/predastore"
"github.com/mulgadc/spinifex/spinifex/services/spinifexui"
"github.com/mulgadc/spinifex/spinifex/services/viperblockd"
"github.com/mulgadc/spinifex/spinifex/services/vpcd"
"github.com/mulgadc/spinifex/spinifex/vpcd"
"github.com/stretchr/testify/assert"
)
-11
View File
@@ -1,11 +0,0 @@
package vpcd
// Phase 1 compat layer: the OVN client mock lives in
// spinifex/network/ovn/mock. These aliases keep existing vpcd tests
// compiling while Phase 2 migrates them to import the mock package directly.
import "github.com/mulgadc/spinifex/spinifex/network/ovn/mock"
type MockOVNClient = mock.Client
var NewMockOVNClient = mock.New
-18
View File
@@ -1,18 +0,0 @@
package vpcd
// Phase 1 compat layer: OVN client lives in spinifex/network/ovn. These
// aliases keep existing vpcd code compiling while Phase 2 migrates callers
// to import the new package directly.
import "github.com/mulgadc/spinifex/spinifex/network/ovn"
type (
OVNClient = ovn.Client
LiveOVNClient = ovn.LiveClient
ACLSpec = ovn.ACLSpec
)
var (
ErrNATNotFound = ovn.ErrNATNotFound
NewLiveOVNClient = ovn.NewLiveClient
)
@@ -17,6 +17,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/admin"
"github.com/mulgadc/spinifex/spinifex/network/external"
"github.com/mulgadc/spinifex/spinifex/network/host"
"github.com/mulgadc/spinifex/spinifex/network/ovn"
"github.com/mulgadc/spinifex/spinifex/network/policy"
"github.com/mulgadc/spinifex/spinifex/network/reconcile"
"github.com/mulgadc/spinifex/spinifex/network/subscribers"
@@ -442,7 +443,7 @@ func launchService(cfg *Config) error {
return fmt.Errorf("OVN NB DB address not configured (ovn_nb_addr is empty)")
}
liveClient := NewLiveOVNClient(cfg.OVNNBAddr)
liveClient := ovn.NewLiveClient(cfg.OVNNBAddr)
ctx := context.Background()
if err := liveClient.Connect(ctx); err != nil {
slog.Error("Failed to connect to OVN NB DB", "endpoint", cfg.OVNNBAddr, "err", err)
@@ -576,9 +577,9 @@ func launchService(cfg *Config) error {
// Startup reconcile: leader-gated read of NATS KV intent state, applied
// once against OVN NB DB. The drift loop below handles the recovery case
// (KV not yet populated when this fires — daemon's EnsureDefaultVPC may
// race with vpcd's startup). Per-event NATS subscribers (TopologyHandler)
// already route through the same network/* managers, so the startup pass
// and the runtime fast path share one implementation.
// race with vpcd's startup). Per-event NATS subscribers route through
// the same network/* managers, so the startup pass and the runtime fast
// path share one implementation.
if isLeader {
intent, intentErr := reconcile.LoadIntentFromKV(ctx, js, cfg.AZ)
if intentErr != nil {