Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion lib/steps/sites.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,11 +281,21 @@ func buildAWSSiteSpec(
}
}

// Karpenter session tolerations.
// Karpenter session placement: allow and direct both Workbench session pods and
// Connect content pods onto the session node pool.
if tolerations := sessionTolerations(clusterCfg.KarpenterConfig); len(tolerations) > 0 {
spec["workbench"] = map[string]interface{}{
"sessionTolerations": tolerations,
}
connectSpec := map[string]interface{}{
"contentTolerations": tolerations,
}
if pool := sessionPoolName(clusterCfg.KarpenterConfig); pool != "" {
connectSpec["contentNodeSelector"] = map[string]string{
"karpenter.sh/nodepool": pool,
}
}
spec["connect"] = connectSpec
}

return spec
Expand Down Expand Up @@ -313,6 +323,21 @@ func sessionTolerations(kc *types.KarpenterConfig) []map[string]interface{} {
return nil
}

// sessionPoolName returns the Name of the first node pool that has
// session_taints=true, or an empty string if none. This mirrors the
// selection logic in sessionTolerations.
func sessionPoolName(kc *types.KarpenterConfig) string {
if kc == nil {
return ""
}
for _, pool := range kc.NodePools {
if pool.SessionTaints {
return pool.Name
}
}
return ""
}
Comment on lines +326 to +339

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: sessionPoolName iterates kc.NodePools independently, but sessionTolerations already does the same scan to find the first session-tainted pool. If those two functions are always called together (as they are in buildAWSSiteSpec), a single helper that returns both the tolerations and the pool name would eliminate duplicate traversal and keep the "find first session-tainted pool" logic in one place. That said, the slice is expected to be short so this is minor — flag it only if you anticipate this pattern growing.


// --- Azure ---

type azureSiteParams struct {
Expand Down
60 changes: 59 additions & 1 deletion lib/steps/sites_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func TestBuildAWSSiteSpecSessionTolerations(t *testing.T) {

clusterCfg := types.AWSWorkloadClusterSpec{
KarpenterConfig: &types.KarpenterConfig{
NodePools: []types.KarpenterNodePool{{SessionTaints: true}},
NodePools: []types.KarpenterNodePool{{Name: "session-pool", SessionTaints: true}},
},
}
spec := buildAWSSiteSpec(params, "arn:aws:secretsmanager:us-east-1:123456789012:secret:db", "20250101", "main", types.SiteConfigSpec{Domain: "d.example.com"}, clusterCfg)
Expand All @@ -244,6 +244,34 @@ func TestBuildAWSSiteSpecSessionTolerations(t *testing.T) {
assert.Equal(t, "Equal", tolerations[0]["operator"])
assert.Equal(t, "session", tolerations[0]["value"])
assert.Equal(t, "NoSchedule", tolerations[0]["effect"])

// Connect content pods receive the same toleration plus a nodeSelector
// onto the session node pool.
connect := spec["connect"].(map[string]interface{})
contentTolerations := connect["contentTolerations"].([]map[string]interface{})
require.Len(t, contentTolerations, 1)
assert.Equal(t, "workload-type", contentTolerations[0]["key"])
assert.Equal(t, "Equal", contentTolerations[0]["operator"])
assert.Equal(t, "session", contentTolerations[0]["value"])
assert.Equal(t, "NoSchedule", contentTolerations[0]["effect"])
nodeSelector := connect["contentNodeSelector"].(map[string]string)
assert.Equal(t, "session-pool", nodeSelector["karpenter.sh/nodepool"])
}

func TestBuildAWSSiteSpecNoConnectWithoutSessionTaints(t *testing.T) {
params := minimalAWSSiteParams("myworkload", []string{"20250101"}, []string{"main"})

clusterCfg := types.AWSWorkloadClusterSpec{
KarpenterConfig: &types.KarpenterConfig{
NodePools: []types.KarpenterNodePool{{Name: "general", SessionTaints: false}},
},
}
spec := buildAWSSiteSpec(params, "arn:aws:secretsmanager:us-east-1:123456789012:secret:db", "20250101", "main", types.SiteConfigSpec{Domain: "d.example.com"}, clusterCfg)

_, hasWorkbench := spec["workbench"]
assert.False(t, hasWorkbench, "workbench should not be set without session taints")
_, hasConnect := spec["connect"]
assert.False(t, hasConnect, "connect should not be set without session taints")
}

// --- sessionTolerations unit tests ---
Expand Down Expand Up @@ -289,6 +317,36 @@ func TestSessionTolerations(t *testing.T) {
})
}

// --- sessionPoolName unit tests ---

func TestSessionPoolName(t *testing.T) {
t.Run("nil config", func(t *testing.T) {
assert.Equal(t, "", sessionPoolName(nil))
})

t.Run("no session taints", func(t *testing.T) {
kc := &types.KarpenterConfig{
NodePools: []types.KarpenterNodePool{{Name: "general", SessionTaints: false}},
}
assert.Equal(t, "", sessionPoolName(kc))
})

t.Run("returns name of first session-tainted pool", func(t *testing.T) {
kc := &types.KarpenterConfig{
NodePools: []types.KarpenterNodePool{
{Name: "general", SessionTaints: false},
{Name: "session-pool", SessionTaints: true},
},
}
assert.Equal(t, "session-pool", sessionPoolName(kc))
})

t.Run("empty node pools", func(t *testing.T) {
kc := &types.KarpenterConfig{NodePools: []types.KarpenterNodePool{}}
assert.Equal(t, "", sessionPoolName(kc))
})
}

// --- NetworkTrustValue tests ---

func TestNetworkTrustValue(t *testing.T) {
Expand Down
Loading