Skip to content

Multiple InitTargets for the same WorkspaceType: race condition causes skipped templates #17

@ifdotpy

Description

@ifdotpy

Problem

When two or more InitTarget resources reference the same WorkspaceType, the init-agent creates separate initcontroller instances for each. Because InitializerForType() derives the initializer name deterministically from the WorkspaceType (not the InitTarget), all controllers share the same initializer name but operate independently.

This causes a race condition: the first controller to finish applying its sources removes the initializer from the LogicalCluster.Status.Initializers list, which causes the second controller to skip its sources entirely (reconciler.go line 64: if !slices.Contains(lc.Status.Initializers, r.initializer) { return }).

Reproduction

  1. Create two InitTargets in the config workspace, both referencing the same WorkspaceType:
apiVersion: initialization.kcp.io/v1alpha1
kind: InitTarget
metadata:
  name: init-rbac
spec:
  workspaceTypeRef:
    path: root
    name: account
  sources:
    - template:
        name: rbac-template
---
apiVersion: initialization.kcp.io/v1alpha1
kind: InitTarget
metadata:
  name: init-quota
spec:
  workspaceTypeRef:
    path: root
    name: account
  sources:
    - template:
        name: quota-template
  1. Create a workspace of type account.
  2. Observe that only one InitTarget's sources are applied; the other is silently skipped.

Root Cause

In targetcontroller/controller.go, getInitTargetKey() returns target.UID, so each InitTarget passes the "controller already exists" check independently (line 129). Each gets its own multicluster manager and initcontroller, but all use the same initializer name from InitializerForType(wst).

The initcontroller in reconciler.go treats the initializer as exclusively owned: once any controller removes it, the others see the initializer is gone and bail out at line 64 without applying their templates.

Impact

  • Templates from one or more InitTargets are silently never applied to new workspaces.
  • The behavior is non-deterministic (depends on which controller wins the race).
  • Users splitting initialization into multiple InitTargets for modularity (e.g., RBAC + quotas + networking) will experience incomplete workspace initialization with no error signal.

Proposed Fix

Change the targetcontroller to key its controllers by WorkspaceType reference (path:name) instead of InitTarget UID. When multiple InitTargets reference the same WorkspaceType:

  1. Single controller per WorkspaceType: Only one multicluster manager and initcontroller is created for each unique WorkspaceType, regardless of how many InitTargets reference it.

  2. Aggregate sources: The InitTargetProvider returns all InitTargets for the WorkspaceType. The initcontroller iterates sources from all of them before considering initialization complete.

  3. Lifecycle management: The controller is only stopped when the last InitTarget for a given WorkspaceType is deleted.

Files affected:

  • internal/controller/targetcontroller/controller.go — key by WSType, aggregate target tracking
  • internal/controller/initcontroller/controller.goInitTargetProvider returns []*InitTarget
  • internal/controller/initcontroller/reconciler.go — iterate all targets' sources, remove initializer only when all complete

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions