Skip to content

Rewrite/watch manager - #340

Open
Tanker2020 wants to merge 44 commits into
IBM:mainfrom
Tanker2020:rewrite/Watch_Manager
Open

Rewrite/watch manager#340
Tanker2020 wants to merge 44 commits into
IBM:mainfrom
Tanker2020:rewrite/Watch_Manager

Conversation

@Tanker2020

Copy link
Copy Markdown

PR-6: Go rewrite — WatchManager, full operator lifecycle, and comprehensive test suite

Summary

This PR completes the Go rewrite of oper8's core operator framework, replacing ~3,000 lines of Python with idiomatic, race-safe Go that delegates watch management, leader election, work-queuing, and health probes entirely to controller-runtime. All 18 packages now compile; 306 tests pass under go test -race ./....


What was ported from Python

Go package Python source Notes
constants constants.py All annotation keys, PassthroughAnnotations, misc constants
errors exceptions.py ConfigError, ClusterError, RolloutError (fatal); PreconditionError, VerificationError (transient); IsFatal() + assert helpers
utils utils.py MergeConfigs, GetNested, SetNested, GetPassthroughAnnotations
status status.py MakeApplicationStatus, UpdateApplicationStatus, StatusChanged, condition/version helpers
dag dag/node.py + dag/graph.py Node, Graph, Runner (concurrent + serial), HaltError, CompletionState
session session.py Per-reconcile context, CR field accessors, component DAG helpers, ScopedName/TruncateName
component component.py Component interface (Name, Disabled, Setup, Deploy, Verify)
controller controller.py Controller interface, BaseController (all hook no-ops), GVK, HookResult
deploymanager deploy_manager/base.py DeployManager interface, DryRunDeployManager (in-memory, thread-safe, watch events), OwnerRef/ApplyOwnerRef
deploymanager/k8s Production k8s.Client: SSA default, Update, Replace, Delete, Get, List, SetStatus
rolloutmanager rollout_manager.py 4-phase rollout (deploy graph → after-deploy hooks → verify graph → after-verify hooks)
reconcilemanager reconcile.py Full reconcile lifecycle: ID gen, session init, finalizer mgmt, preconditions, setup/finalize, rollout, status update, requeue
verify verify_resources.py VerifyResource, VerifyPod, VerifyJob, VerifyDeployment, VerifyStatefulSet, VerifySubsystem, kind registry
patch patch.py + patch_strategic_merge.py Apply() with SMP (typed schema for Deployment/StatefulSet/etc.) + JSON-6902 (RFC 6902), component-name routing
temporarypatch temporary_patch/ Component (add/remove patch annotation on target CR), Controller (finalizer, patchable-kinds allowlist)
watchmanager watch_manager.py See §WatchManager Improvements below
cmd/run.go cmd/run_operator_cmd.py RunOperator(Options): wires manager, predicates, health/readyz probes, leader election, signal handling

WatchManager: from ~900 Python lines to ~200 Go lines

The Python WatchManager was the largest and most complex module — it implemented its own watch streams, work queue, rate limiting, leader election, subprocess-per-reconcile isolation, requeue backoff, and health probes, all from scratch.

In Go, controller-runtime provides all of that natively. The watchmanager package is now a thin adapter of ~200 lines:

Concern Python WatchManager Go WatchManager
Watch streams Custom (k8s-client-python) controller-runtime informer cache
Work queue + rate limiting threading.Queue + custom controller-runtime workqueue
Leader election Custom Lease implementation ctrl.Options{LeaderElection: true}
Requeue / backoff Custom sleep-poll loop controller-runtime exponential backoff
Subprocess isolation Fork per reconcile (GIL workaround) Not needed — goroutines are race-safe
Health/readyz probes Separate HTTP server mgr.AddHealthzCheck / AddReadyzCheck
Pause filter Checked inside reconcile (wastes queue slot) NotPaused predicate — never enqueued
Generation filter Partial (missed deletion edge case) GenerationChangedOrDeleted predicate — gen change or DeletionTimestamp
Lines of code ~900 ~200

New capabilities not in the Python version

  • Race-safe concurrent DAG runner — goroutines + buffered results channel + sync.Mutex, verified clean under go test -race
  • Predicate-level filteringNotPaused and GenerationChangedOrDeleted prevent spurious objects from ever reaching the reconcile queue
  • Server-side apply (SSA) as the default deploy method — field ownership tracking and conflict detection out of the box; test fallback for the controller-runtime fake client
  • Structured GVK routingschema.GroupVersionKind structs everywhere; GVKFromString validates format at startup
  • DryRunDeployManager — fully thread-safe in-memory cluster simulator with watch event emission; used in all unit tests; no cluster needed

Bugs fixed during this PR

  1. errors.IsFatal() always returned false for fatal errors — type-asserted err.(*Oper8Error) but concrete types are *ConfigError / *ClusterError etc. (embedded structs). Fixed by asserting against a fatalChecker interface.

  2. patch.resolvePatchPayload routing was inverted — patch entries without a matching component-name key were silently skipped instead of returning nil. Tests corrected to nest patch payloads under the internalName routing key, matching Python semantics.

  3. TestApply_UnsupportedPatchType never exercised the switch — the test passed an empty map payload; the resolver found no key and returned nil, bypassing the type-switch entirely. Test fixed to use a resolvable payload.


Test coverage

306 tests across 16 packages (all passing, go test -race ./...):

Package Tests Highlights
watchmanager 40 Adapter (happy path, NotFound, finalizer, requeue, setup error, paused object, cross-namespace), GenerationChangedOrDeleted (8 cases), NotPaused (6 cases), GVKFromString (6 cases)
reconcilemanager 19 Empty graph, verified, setup error, deploy error, verify-not-ready, precondition blocking, ordered components, invalid CR, finalizer path, ShouldRequeue override, ManageStatus, multiple preconditions
rolloutmanager 18 Empty graph, happy path, setup/deploy errors, downstream blocking, independent branches, concurrent, hooks (all 4 variants), disabled component
deploymanager/k8s 17 Get, Deploy (SSA/Update/Replace), Delete, List, Watch, edge cases
deploymanager 16 DryRun: CRUD, watch events, label selector, deep-copy isolation, owner refs
dag 25+ Serial + concurrent runner, fatal/unverified halts, disabled nodes, execution order, edge funcs, context cancellation, race detector (20 goroutines), HaltError.Unwrap, GetNode, Nodes() root exclusion
status 20 MakeApplicationStatus (all reason combos), UpdateApplicationStatus (preserve/override), StatusChanged (timestamp-ignore), GetVersion, ComponentStatus sorting, IBM CloudPak kind field
verify 28+ Pod/Job/Deployment/StatefulSet/Subsystem verifiers, kind registry, latest-condition sort, custom condition type, custom timestamp key, per-call VerifyFunc overrides registry
session 19 Construction, validation (5 error paths), accessors, current version, component DAG helpers, ScopedName/TruncateName (determinism, collision-resistance, limit boundary)
patch 18 SMP + JSON-6902, routing (match/no-match/dotted/deeply-nested), multiple sequential patches, immutability, remove op
errors 18 IsFatal for all 5 types via error interface, nil, non-Oper8 error, formatted messages, assert helpers
controller 13 All BaseController defaults, all hook methods, GVK, HookResult
utils 10 MergeConfigs deep/shallow, GetNested/SetNested, GetPassthroughAnnotations
constants 15 Key format, distinctness, PassthroughAnnotations membership/prefix/deduplication, value assertions
component 14 Full interface contract, call counting, error propagation, full lifecycle
temporarypatch 8 Component (add/remove annotation), Controller (GVK/finalizer/missing-spec/unpatchable-kind)

What still needs to be done (follow-on PRs)

  • make generate — run controller-gen to replace the hand-written zz_generated.deepcopy.go stub
  • make manifests — populate config/crd/bases/ with real CRD YAML (needed by OLM and integration tests)
  • cmd/run.go tests — smoke test for RunOperator() option validation and manager wiring
  • go.mod stabilisation — an external process keeps bumping to alpha k8s deps; pin to v0.31.0 stable once resolved
  • Namespace-scoped watch — pass Namespaces []string through watchmanager.Options into ctrl.Options{Cache: ...} for namespace-restricted operators

How to test

cd oper8/rewrite
go mod tidy
go test -race ./...   # 306 tests, all green

- Node: name, optional NodeFunc, directed edges to upstream deps
- Edge: carries optional EdgeFunc to gate dependent start
- ResourceNode: embeds Node, adds Manifest/VerifyFunc/DeployMethod
- Graph: root-anchored container; AddNode, AddDependency, Topology
- Cycle detection on every AddChild call (DFS reachability)
- Topology() returns DFS post-order (dependency-first deploy order)

Files: rewrite/dag/node.go, rewrite/dag/graph.go
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
- CompletionState: Verified/Unverified/Failed/Unstarted node buckets
- DeployCompleted(), VerifyCompleted(), AnyFailed() predicates
- HaltError{Fatal bool}: returned by NodeFunc to signal runner halt
  Fatal=true  → node lands in Failed, downstreams become Unstarted
  Fatal=false → node lands in Unverified (deployed, not yet ready)

Files: rewrite/dag/completion_state.go
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Python used ThreadPoolExecutor + time.sleep(0.05) busy-poll loop.
Go port uses goroutines + buffered results channel; scheduler blocks
on select — zero CPU busy-polling.

- NewRunner(graph, opts...) with functional options
- WithConcurrency(0): serial topology walk, no goroutines (dry-run/test)
- WithConcurrency(n): semaphore-capped parallel execution
- WithVerifyUpstream(bool): gate dependent start on EdgeFunc result
- context.Context cancellation: drains in-flight, marks rest Unstarted
- Independent graph branches continue executing after sibling failure
  (matches Python oper8 intended behaviour; Python had a bug where the
  serial loop broke early on fatalErr)
- stateMap protected by sync.Mutex; scheduler is single writer

Files: rewrite/dag/runner.go
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
23 test cases covering:
  Graph/Node:  empty graph, duplicate node, empty name, cycle detection,
               self-loop, topology order, String()
  Runner serial: all succeed, empty graph, fatal halt (independent branch
               still runs), unverified halt, disabled node, execution order
  Runner concurrent: all succeed, fatal halt, independent nodes verified
               parallel via start-time spread, race detector stress test
               (20 nodes, atomic counter), context cancellation
  EdgeFunc:    blocks dependent when returns false, allows when true
  CompletionState: all predicate combinations
  ResourceNode: construction and field access

Concurrency test uses start-time recording rather than wall-clock
total elapsed — CI-safe on slow runners.

Files: rewrite/dag/runner_test.go
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
- Matrix: Go 1.22 and 1.23
- go test -race -count=1 -timeout=60s ./dag/...
- go build ./... and go vet ./dag/...
- golangci-lint on dag/ package
- Triggered on push to rewrite/DAG_Runner and PRs targeting main
- working-directory: rewrite (module root)

Files: ./.github/workflows/pr1-dag-runner.yml
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Defines the core abstraction all cluster interactions go through.

Python (bool, bool) return tuples → Go (changed bool, err error):
- success bool dropped; errors are returned as error values
- callers use idiomatic `if err != nil` instead of checking two booleans

watch_objects Python generator → Go channel:
- Watch() returns <-chan WatchEvent; caller ranges over it
- Cancelled via context.Context; channel is closed on cancel

New types vs Python:
- ListOptions struct (replaces positional label_selector/field_selector args)
- EventType string constants (ADDED/MODIFIED/DELETED)
- WatchEvent struct with Timestamp

Files: rewrite/deploymanager/deploymanager.go

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Ports deploy_manager/owner_references.py.

- OwnerRef(ownerCR) builds a single ownerReference map entry
- ApplyOwnerRef(owner, child) stamps the reference onto child.metadata
  - No-op when owner == child (same UID)
  - No-op for cross-namespace references (K8s does not support them)
  - Idempotent: will not add duplicate entries
- blockOwnerDeletion: true; controller field intentionally omitted
  (matches Python behaviour and StackOverflow rationale in source)

Files: rewrite/deploymanager/ownerref.go
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Ports deploy_manager/dry_run_deploy_manager.py.

Primary use: unit-testing controllers without a live cluster.

Key differences from Python:
- Python used nested defaultdict; Go uses typed clusterStore
  (map[ns][kind][apiVersion][name] → object)
- Python RLock on class level; Go sync.RWMutex per instance
- Python watch callbacks were registered functions; Go uses buffered
  channels — consumers range over the channel, cancel via context
- Watch channel is closed when ctx is cancelled (no explicit Unregister)
- deepCopy via JSON marshal/unmarshal (simple, correct for map[string]any)
- matchSelector implements = == != existence operators (sufficient for
  dry-run tests; full set-based selector is future work)

Extra test helpers (not in Python):
- GetStored(ns, kind, av, name) — direct store access for assertions
- ObjectCount() — total objects in store

Files: rewrite/deploymanager/dryrun.go
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
19 test cases covering:
  Deploy:     create, idempotent re-deploy, field update, owner ref stamping
  Get:        not found returns nil, found returns deep copy (mutation check)
  Delete:     existing object, non-existent no-op
  List:       all objects, label selector filtering
  SetStatus:  sets status, returns changed=true; error on missing object
  Watch:      receives ADDED on deploy, DELETED on delete, channel closes
              on context cancel (race-detector safe)
  OwnerRef:   stamps reference, idempotent, cross-namespace skipped

Files: rewrite/deploymanager/dryrun_test.go
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
- Matrix: Go 1.22 and 1.23
- go test -race -count=1 -timeout=60s ./deploymanager/...
- go build ./... and go vet ./deploymanager/...
- golangci-lint on deploymanager/ package
- Triggered on push to rewrite/Deploy_Manager and PRs targeting main

Files: .github/workflows/pr2-deploy-manager.yml
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…licationStatus

Ports oper8 Python status.py to Go.

Reason types: ReadyReason, UpdatingReason, ServiceStatus string constants.

MakeApplicationStatus(Options) builds a complete status map:
- Ready + Updating conditions from reason/message pairs
- External conditions preserved alongside oper8-managed ones
- ComponentStatus block from dag.CompletionState (sorted node names)
- versions.reconciled / versions.available.versions (IBM CloudPak paths)
- <kind>Status field (e.g. customerStatus) when Kind is set

UpdateApplicationStatus merges new Options onto existing status,
carrying forward current reasons and external conditions when not overridden.

GetCondition, GetVersion, StatusChanged helper functions included.

Python translation notes:
- deepdiff library dropped; StatusChanged uses recursive JSON comparison
  after stripping lastTransactionTime keys — zero external dependencies
- **kwargs replaced by Options struct (compile-time field checking)
- aconfig nested_set/nested_get replaced by nestedSet/nestedGet dot-path helpers

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
MakeApplicationStatus: Ready/Updating condition status values for all
  reason combinations, empty options, external conditions, version fields,
  componentStatus deployed/verified counts and dependencyGraph, IBM CloudPak
  <kind>Status (Completed/Failed/InProgress/custom preserved)

UpdateApplicationStatus: preserves existing reasons when not overridden,
  overrides when provided, preserves external conditions and top-level fields

StatusChanged: same content + different timestamps not changed, different
  reason changed, nil inputs, added field

GetCondition, GetVersion: found/missing cases
UpdatingReason active/inactive matrix (all 6 reasons)
ComponentStatus node names sorted alphabetically

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Matrix Go 1.22 and 1.23, race detector, golangci-lint v1.64.8

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…load config

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…ondition

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…rity tests

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…r, ReconcileManager

Ports oper8 Python session.py / component.py / controller.py /
rollout_manager.py / reconcile.py to Go.

New packages
  rewrite/session/          per-reconcile context (CR manifest, DAG, status)
  rewrite/component/        Component interface (Setup / Deploy / Verify)
  rewrite/controller/       Controller interface + BaseController no-op embed
  rewrite/rolloutmanager/   4-phase loop: deploy→after_deploy→verify→after_verify
  rewrite/reconcilemanager/ top-level orchestrator: ID gen, session init,
                            preconditions, rollout, status writes, finalizers

dag/node.go   Node.SetFunc / SetData / Data (needed by RolloutManager)
dag/runner.go Runner.CompletionState(); remove unused inFlight int64 field
gofmt         dag/runner_test.go, deploymanager/dryrun.go

reconcilemanager tests (9 cases, -race):
  EmptyGraph, SingleComponentVerified, SetupError, DeployError,
  VerifyNotReady, Precondition, TwoComponentsOrdered, InvalidCR, Finalizer

CI: .github/workflows/pr5-reconcile.yml — go test -race ./... + golangci-lint
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
dag/runner.go:
  Remove unused struct field  — this was the field
  golangci-lint flagged. The concurrent scheduler uses a local variable
   inside runConcurrent; the struct field was never read
  or written and should never have been there.

session/session_test.go:       21 tests (lost in branch switch, recreated)
controller/controller_test.go: 14 tests (lost in branch switch, recreated)
rolloutmanager/rolloutmanager_test.go: 18 tests (lost in branch switch, recreated)

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…0:00)

nishanthk  ttys002                         Thu Jul 30 11:30 - 11:30  (00:00)
nishanthk  ttys002                         Thu Jul 30 11:30 - 11:30  (00:00)
nishanthk  ttys002                         Thu Jul 30 11:30 - 11:30  (00:00)
nishanthk  ttys002                         Thu Jul 30 11:30 - 11:30  (00:00)
nishanthk  ttys002                         Thu Jul 30 11:29 - 11:29  (00:00)
nishanthk  ttys002                         Thu Jul 30 11:29 - 11:29  (00:00)
nishanthk  ttys002                         Thu Jul 30 11:28 - 11:28  (00:00)
nishanthk  ttys002                         Thu Jul 30 11:23 - 11:23  (00:00)
nishanthk  ttys001                         Thu Jul 30 11:19   still logged in
nishanthk  ttys000                         Thu Jul 30 11:19   still logged in
nishanthk  console                         Thu Jul 30 11:19   still logged in
reboot time                                Thu Jul 30 11:00
shutdown time                              Thu Jul 30 10:59
nishanthk  ttys001                         Fri Jul 24 15:26 - 15:26  (00:00)
nishanthk  ttys000                         Fri Jul 24 15:26 - 15:26  (00:00)
nishanthk  console                         Fri Jul 24 15:26 - 10:59 (5+19:33)
reboot time                                Fri Jul 24 15:25
shutdown time                              Fri Jul 24 15:24
nishanthk  ttys001                         Wed Jul 22 00:37 - 00:37  (00:00)
nishanthk  ttys000                         Wed Jul 22 00:37 - 00:37  (00:00)
nishanthk  console                         Wed Jul 22 00:37 - 15:24 (2+14:47)
reboot time                                Wed Jul 22 00:35
shutdown time                              Wed Jul 22 00:31
root       console                         Wed Jul 22 00:30 - shutdown  (00:00)
nishanthk  ttys001                         Fri Jul 17 16:28 - 16:28  (00:00)
nishanthk  ttys001                         Thu Jul  9 13:25 - 13:25  (00:00)
nishanthk  ttys001                         Mon Jul  6 14:14 - 14:14  (00:00)
nishanthk  ttys005                         Mon Jul  6 14:14 - 14:14  (00:00)
nishanthk  ttys006                         Mon Jul  6 14:14 - 14:14  (00:00)
nishanthk  ttys003                         Mon Jun 29 14:47 - 14:47  (00:00)
nishanthk  ttys002                         Mon Jun 29 14:47 - 14:47  (00:00)
nishanthk  ttys001                         Mon Jun 29 14:47 - 14:47  (00:00)
nishanthk  ttys006                         Mon Jun 29 14:21 - 14:21  (00:00)
nishanthk  ttys006                         Mon Jun 29 14:21 - 14:21  (00:00)
nishanthk  ttys003                         Mon Jun 29 13:36 - 13:36  (00:00)
nishanthk  ttys005                         Mon Jun 22 02:32 - 02:32  (00:00)
nishanthk  ttys004                         Mon Jun 22 02:30 - 02:30  (00:00)
nishanthk  ttys003                         Mon Jun 22 01:16 - 01:16  (00:00)
nishanthk  ttys002                         Thu Jun 18 16:23 - 16:23  (00:00)
nishanthk  ttys001                         Thu Jun 18 16:23 - 16:23  (00:00)
nishanthk  ttys000                         Thu Jun 18 16:23 - 16:23  (00:00)
nishanthk  console                         Thu Jun 18 16:23 - 00:30 (33+08:07)
reboot time                                Thu Jun 18 16:23
nishanthk  ttys002                         Wed Jun 17 12:32 - crash (1+03:50)
nishanthk  ttys001                         Mon Jun 15 13:30 - crash (3+02:52)
nishanthk  ttys000                         Mon Jun 15 13:30 - crash (3+02:53)
nishanthk  ttys003                         Mon Jun 15 13:17 - 13:17  (00:00)
nishanthk  ttys003                         Mon Jun 15 13:17 - 13:17  (00:00)
nishanthk  ttys002                         Mon Jun 15 13:16 - 13:16  (00:00)
nishanthk  ttys003                         Mon Jun 15 13:14 - 13:14  (00:00)
nishanthk  ttys002                         Mon Jun 15 13:12 - 13:12  (00:00)
nishanthk  ttys002                         Mon Jun 15 13:11 - 13:11  (00:00)
nishanthk  ttys003                         Mon Jun 15 13:07 - 13:07  (00:00)
nishanthk  ttys002                         Fri Jun 12 13:02 - 13:02  (00:00)
nishanthk  ttys002                         Thu Jun 11 12:44 - 12:44  (00:00)
nishanthk  ttys004                         Mon Jun  8 13:56 - 13:56  (00:00)
nishanthk  ttys002                         Mon Jun  8 13:56 - 13:56  (00:00)
nishanthk  ttys003                         Mon Jun  8 13:56 - 13:56  (00:00)
nishanthk  ttys004                         Fri Jun  5 09:34 - 09:34  (00:00)
nishanthk  ttys002                         Fri Jun  5 09:34 - 09:34  (00:00)
nishanthk  ttys003                         Fri Jun  5 09:34 - 09:34  (00:00)
nishanthk  ttys020                         Thu Jun  4 11:20 - 11:20  (00:00)
nishanthk  ttys004                         Wed Jun  3 16:42 - 16:42  (00:00)
nishanthk  ttys003                         Wed Jun  3 00:06 - 00:06  (00:00)
nishanthk  ttys002                         Wed Jun  3 00:06 - 00:06  (00:00)
nishanthk  ttys001                         Wed Jun  3 00:06 - 00:06  (00:00)
nishanthk  ttys000                         Wed Jun  3 00:06 - 00:06  (00:00)
nishanthk  console                         Wed Jun  3 00:06 - crash (15+16:17)
reboot time                                Wed Jun  3 00:04
shutdown time                              Tue Jun  2 23:59
root       console                         Tue Jun  2 23:57 - shutdown  (00:02)
nishanthk  ttys003                         Mon Jun  1 21:50 - 21:50  (00:00)
nishanthk  ttys002                         Mon Jun  1 21:50 - 21:50  (00:00)
nishanthk  ttys001                         Mon Jun  1 21:50 - 21:50  (00:00)
nishanthk  ttys000                         Mon Jun  1 21:50 - 21:50  (00:00)
nishanthk  console                         Mon Jun  1 21:50 - 23:57 (1+02:07)
reboot time                                Mon Jun  1 21:49
shutdown time                              Mon Jun  1 21:49
root       console                         Mon Jun  1 21:49 - shutdown  (00:00)
nishanthk  ttys003                         Mon Jun  1 10:57 - 10:57  (00:00)
nishanthk  ttys009                         Mon Jun  1 10:21 - 10:21  (00:00)
nishanthk  ttys000                         Mon Jun  1 10:15 - 10:15  (00:00)
nishanthk  ttys004                         Fri May 29 15:11 - 15:11  (00:00)
nishanthk  ttys003                         Fri May 29 12:56 - 12:56  (00:00)
nishanthk  ttys002                         Wed May 27 16:07 - 16:07  (00:00)
nishanthk  ttys001                         Wed May 27 16:05 - 16:05  (00:00)
nishanthk  ttys001                         Wed May 27 11:31 - 11:32  (00:00)
nishanthk  ttys001                         Wed May 27 11:31 - 11:31  (00:00)
nishanthk  ttys002                         Tue May 26 17:36 - 17:36  (00:00)
nishanthk  ttys001                         Tue May 26 17:24 - 17:24  (00:00)
nishanthk  ttys001                         Tue May 26 15:48 - 15:48  (00:00)
nishanthk  ttys001                         Tue May 26 15:47 - 15:47  (00:00)
nishanthk  ttys000                         Tue May 26 15:46 - 15:46  (00:00)
nishanthk  ttys001                         Tue May 26 15:42 - 15:42  (00:00)
nishanthk  ttys001                         Tue May 26 15:31 - 15:31  (00:00)
nishanthk  ttys000                         Tue May 26 14:44 - 14:44  (00:00)
nishanthk  ttys002                         Tue May 26 14:43 - 14:43  (00:00)
nishanthk  ttys002                         Tue May 26 14:43 - 14:43  (00:00)
nishanthk  ttys002                         Tue May 26 14:43 - 14:43  (00:00)
nishanthk  ttys002                         Tue May 26 14:43 - 14:43  (00:00)
nishanthk  ttys002                         Tue May 26 14:43 - 14:43  (00:00)
nishanthk  ttys002                         Tue May 26 14:43 - 14:43  (00:00)
nishanthk  ttys002                         Tue May 26 14:43 - 14:43  (00:00)
nishanthk  ttys002                         Tue May 26 14:43 - 14:43  (00:00)
nishanthk  ttys002                         Tue May 26 14:43 - 14:43  (00:00)
nishanthk  ttys002                         Tue May 26 14:43 - 14:43  (00:00)
nishanthk  ttys002                         Tue May 26 14:43 - 14:43  (00:00)
nishanthk  ttys001                         Tue May 26 14:42 - 14:42  (00:00)
nishanthk  ttys000                         Tue May 26 14:31 - 14:31  (00:00)
nishanthk  console                         Tue May 26 13:42 - 21:49 (6+08:07)
_mbsetupuser console                         Tue May 26 13:27 - 13:42  (00:14)
root       console                         Tue May 26 13:27 - 13:27  (00:00)
reboot time                                Tue May 26 13:26
shutdown time                              Thu May 21 02:12
reboot time                                Thu May 21 02:03
reboot time                                Tue Mar  3 22:23
reboot time                                Tue Mar  3 22:18

wtmp begins Tue Mar  3 22:18:14 CST 2026 struct field to pass golangci-lint

The  field on Runner was written but never read
externally — Run() already returns *CompletionState directly. The
 linter correctly flags any struct field that is never read.
Removing it fixes the CI lint failure.

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…efault

- Add Disabled() bool to Component interface; RolloutManager skips
  Setup/Deploy/Verify for disabled components (no-op DAG success)
- Fix requeue: ReconcileManager now uses !VerifyCompleted()||ShouldRequeue
  instead of ShouldRequeue alone; BaseController.ShouldRequeue → false
- Fix addFinalizer/removeFinalizer: DeployMethodUpdate → DeployMethodDefault
  (existing-wins merge was silently dropping the mutated finalizer list)
- Expand reconcilemanager tests 9→24 cases; add 2 disabled-component
  tests to rolloutmanager

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
… dead code

- rolloutmanager: fix append aliasing bug in CompletionState assembly;
  deployedAll now uses make+copy instead of append on a shared backing array
- rolloutmanager: remove redundant second unverifiedNodes loop (verifyState.Unverified
  is a strict subset of deployedAll; second loop could never add anything)
- rolloutmanager: correct stale package doc (Disabled() is now first-class)
- reconcilemanager: fix nil-map panic in updateCompletionStatus; GetCondition
  returns nil on a fresh CR — guard before map index
- reconcilemanager: fix ManageStatus doc comment ("Default true" → zero value is false)
- controller: fix ShouldRequeue interface doc (claimed "(true, 0)" multi-return
  for a method that returns bool)
- reconcilemanager_test: collapse two duplicate RequeueAfter tests into one;
  remove time import kept alive only by _ = time.Second

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
… Makefile, CI

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
… Makefile, CI

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
…ests

Port all remaining oper8 Python modules to Go. Replace ~900-line
WatchManager with a ~200-line controller-runtime adapter. Add comprehensive
test suite (306 tests, go test -race ./... clean).

Packages ported
- constants: all annotation keys, PassthroughAnnotations, misc constants
- errors: 5-type hierarchy (ConfigError/ClusterError/RolloutError fatal;
  PreconditionError/VerificationError transient); IsFatal() + assert helpers
- utils: MergeConfigs (deep), GetNested/SetNested (dotted keys),
  GetPassthroughAnnotations
- status: MakeApplicationStatus, UpdateApplicationStatus, StatusChanged
  (timestamp-ignoring diff), GetVersion, IBM CloudPak kind field
- dag: Graph, Node, concurrent+serial Runner, HaltError, CompletionState;
  verified race-safe under go test -race
- session: per-reconcile context, CR validation, ScopedName/TruncateName
- component: Component interface (Name/Disabled/Setup/Deploy/Verify)
- controller: Controller interface + BaseController (all hook no-ops)
- deploymanager: DeployManager interface; DryRunDeployManager (thread-safe,
  watch events); OwnerRef/ApplyOwnerRef
- deploymanager/k8s: production client (SSA default, Update, Replace,
  Delete, Get, List, SetStatus); SSA→Create fallback for fake client in tests
- rolloutmanager: 4-phase rollout (deploy, after-deploy, verify, after-verify)
- reconcilemanager: full reconcile lifecycle (ID, session, finalizer,
  preconditions, setup/finalize, rollout, status, requeue)
- verify: VerifyResource, VerifyPod/Job/Deployment/StatefulSet/Subsystem,
  kind registry, condition sort, per-call VerifyFunc, custom timestamp key
- patch: Apply() with SMP (typed schema) + JSON-6902; component-name routing
- temporarypatch: Component (patch annotation add/remove) + Controller
  (finalizer, patchable-kinds allowlist)
- watchmanager: Adapter (implements reconcile.Reconciler), predicates
  (GenerationChangedOrDeleted, NotPaused), GVKFromString
- cmd/run.go: RunOperator() — wires manager, probes, leader election

WatchManager delta vs Python
- Python: ~900 lines (custom watch/queue/leader-election/backoff/isolation)
- Go: ~200 lines (delegates everything to controller-runtime)
- Pause filter moved from inside reconcile to predicate layer (never enqueued)
- Generation filter fixed: passes spec changes AND deletions with same gen
- Subprocess isolation removed: goroutines + -race make it unnecessary

Bug fixes
- errors.IsFatal(): type-assert was *Oper8Error but concrete types are
  *ConfigError etc.; fixed to interface check
- patch.resolvePatchPayload: routing semantics corrected (nil on no-match)
- TestApply_UnsupportedPatchType: empty-map payload bypassed type-switch;
  test corrected to use a resolvable payload

New test files: constants/constants_test.go, component/component_test.go
Expanded: errors, patch, dag, session, verify (+67 tests across 5 packages)

Total: 306 tests, 16 packages, go test -race ./...

Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
Signed-off-by: Nishanth Kolakalapudi <Nishanth.Kol@ibm.com>
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.

1 participant