Skip to content

Commit be9e732

Browse files
author
tazhate
committed
fix(e2e): fix remaining E2E failures — missing CRD + node affinity
Two more E2E issues after the pod security / storage class fix: 1. ChainVersionCatalog CRD was missing from config/crd/bases/ — controller manager failed to start because the CRD informer couldn't find the kind. Ran make manifests to regenerate; removed stale nodes.k8s-bch.io_* files left from old API group (generated by controller-gen scanning .claude/ worktree; .claude/ is in .gitignore so only affects local runs). 2. DefaultNodeSelector returned {"node-type": "heavy"} for generic groups (light/medium/heavy/archive) — pods couldn't schedule in Kind because the single control-plane node has no such label. Changed default case to return nil so pods schedule on any available node. Specialised groups (storage, blockchain) keep their explicit selectors. Updated adapter and controller tests to match the new behaviour. Reviewed Kind cluster node labels and controller-gen path scanning behaviour.
1 parent eace7db commit be9e732

4 files changed

Lines changed: 294 additions & 21 deletions

File tree

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
controller-gen.kubebuilder.io/version: v0.17.1
7+
name: chainversioncatalogs.nodes.chainplane.io
8+
spec:
9+
group: nodes.chainplane.io
10+
names:
11+
kind: ChainVersionCatalog
12+
listKind: ChainVersionCatalogList
13+
plural: chainversioncatalogs
14+
singular: chainversioncatalog
15+
scope: Cluster
16+
versions:
17+
- additionalPrinterColumns:
18+
- jsonPath: .spec.chain
19+
name: Chain
20+
type: string
21+
- jsonPath: .status.latestTag
22+
name: Latest
23+
type: string
24+
- jsonPath: .status.checkedAt
25+
name: CheckedAt
26+
type: date
27+
name: v1alpha1
28+
schema:
29+
openAPIV3Schema:
30+
description: |-
31+
ChainVersionCatalog is a cluster-scoped resource that tracks the latest
32+
available image version for a given blockchain adapter by polling the
33+
upstream container registry on a configurable interval.
34+
properties:
35+
apiVersion:
36+
description: |-
37+
APIVersion defines the versioned schema of this representation of an object.
38+
Servers should convert recognized schemas to the latest internal value, and
39+
may reject unrecognized values.
40+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
41+
type: string
42+
kind:
43+
description: |-
44+
Kind is a string value representing the REST resource this object represents.
45+
Servers may infer this from the endpoint the client submits requests to.
46+
Cannot be updated.
47+
In CamelCase.
48+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
49+
type: string
50+
metadata:
51+
type: object
52+
spec:
53+
description: ChainVersionCatalogSpec defines the desired state of ChainVersionCatalog.
54+
properties:
55+
chain:
56+
description: Chain identifies the blockchain whose image versions
57+
are tracked.
58+
enum:
59+
- ethereum
60+
- ethereum-archive
61+
- ethereum-beacon
62+
- bitcoin
63+
- solana
64+
- bsc
65+
- tron
66+
- polygon
67+
- avalanche
68+
- litecoin
69+
- xrp
70+
- stellar
71+
- dash
72+
- ton
73+
- cosmos
74+
- near
75+
- sui
76+
- aptos
77+
- cardano
78+
- arbitrum
79+
- optimism
80+
- base
81+
- fantom
82+
- gnosis
83+
- gnosis-beacon
84+
- mantle
85+
- zksync
86+
- linea
87+
- scroll
88+
- berachain
89+
- cronos
90+
- ronin
91+
- celo
92+
- blast
93+
- mode
94+
- zora
95+
- taiko
96+
- dogecoin
97+
- osmosis
98+
- sei
99+
- evmos
100+
- kava
101+
- polkadot
102+
- starknet
103+
- filecoin
104+
- moonbeam
105+
- moonriver
106+
- polygon-zkevm
107+
- manta-pacific
108+
- metis
109+
- fraxtal
110+
- lisk
111+
- kroma
112+
- bob
113+
- boba-eth
114+
- soneium
115+
- swell
116+
- superseed
117+
- ink
118+
- morph
119+
- worldchain
120+
- unichain
121+
- lens
122+
- plume
123+
- hemi
124+
- abstract
125+
- megaeth
126+
- zero-network
127+
- zircuit
128+
- immutable-zkevm
129+
- axelar
130+
- dymension
131+
- aurora
132+
- harmony
133+
- rootstock
134+
- telos
135+
- klaytn
136+
- shibarium
137+
- core
138+
- haqq
139+
- hashkey
140+
- ethereum-classic
141+
- opbnb
142+
- fuse
143+
- thundercore
144+
- wemix
145+
- viction
146+
- cronos-zkevm
147+
- sonic
148+
- goat
149+
- katana
150+
- mezo
151+
- plasma
152+
- playnance
153+
- kusama
154+
- hyperliquid
155+
- monad
156+
type: string
157+
checkInterval:
158+
description: |-
159+
CheckInterval is how often to poll the registry (e.g. "6h", "30m").
160+
Defaults to 6h when empty.
161+
type: string
162+
client:
163+
description: |-
164+
Client selects the client implementation (e.g. "reth", "geth") for chains
165+
that support multiple clients. Leave empty for single-client chains.
166+
type: string
167+
required:
168+
- chain
169+
type: object
170+
status:
171+
description: ChainVersionCatalogStatus describes the observed state of
172+
ChainVersionCatalog.
173+
properties:
174+
checkedAt:
175+
description: CheckedAt is when the operator last polled the registry.
176+
format: date-time
177+
type: string
178+
conditions:
179+
description: Conditions reflect the current reconciliation state.
180+
items:
181+
description: Condition contains details for one aspect of the current
182+
state of this API Resource.
183+
properties:
184+
lastTransitionTime:
185+
description: |-
186+
lastTransitionTime is the last time the condition transitioned from one status to another.
187+
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
188+
format: date-time
189+
type: string
190+
message:
191+
description: |-
192+
message is a human readable message indicating details about the transition.
193+
This may be an empty string.
194+
maxLength: 32768
195+
type: string
196+
observedGeneration:
197+
description: |-
198+
observedGeneration represents the .metadata.generation that the condition was set based upon.
199+
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
200+
with respect to the current state of the instance.
201+
format: int64
202+
minimum: 0
203+
type: integer
204+
reason:
205+
description: |-
206+
reason contains a programmatic identifier indicating the reason for the condition's last transition.
207+
Producers of specific condition types may define expected values and meanings for this field,
208+
and whether the values are considered a guaranteed API.
209+
The value should be a CamelCase string.
210+
This field may not be empty.
211+
maxLength: 1024
212+
minLength: 1
213+
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
214+
type: string
215+
status:
216+
description: status of the condition, one of True, False, Unknown.
217+
enum:
218+
- "True"
219+
- "False"
220+
- Unknown
221+
type: string
222+
type:
223+
description: type of condition in CamelCase or in foo.example.com/CamelCase.
224+
maxLength: 316
225+
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
226+
type: string
227+
required:
228+
- lastTransitionTime
229+
- message
230+
- reason
231+
- status
232+
- type
233+
type: object
234+
type: array
235+
latestDigest:
236+
description: LatestDigest is the image digest of LatestTag (may be
237+
empty for GHCR).
238+
type: string
239+
latestTag:
240+
description: LatestTag is the most recent semver-compatible tag found
241+
in the registry.
242+
type: string
243+
publishedAt:
244+
description: PublishedAt is when LatestTag was pushed to the registry.
245+
format: date-time
246+
type: string
247+
recentTags:
248+
description: RecentTags lists the newest tags found (up to 10), sorted
249+
newest-first.
250+
items:
251+
description: TagInfo holds information about a single registry tag.
252+
properties:
253+
publishedAt:
254+
format: date-time
255+
type: string
256+
tag:
257+
type: string
258+
required:
259+
- tag
260+
type: object
261+
type: array
262+
type: object
263+
type: object
264+
served: true
265+
storage: true
266+
subresources:
267+
status: {}

internal/adapters/adapters_test.go

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -303,13 +303,16 @@ func TestAllAdaptersLivenessProbe(t *testing.T) {
303303
// ---------------------------------------------------------------------------
304304

305305
func TestAllAdaptersNodeSelector(t *testing.T) {
306-
groups := []nodesv1alpha1.NodeGroup{
306+
// Specialised groups must return non-empty selectors; generic groups may return nil.
307+
specialised := []nodesv1alpha1.NodeGroup{
308+
nodesv1alpha1.NodeGroupStorage,
309+
nodesv1alpha1.NodeGroupBlockchain,
310+
}
311+
generic := []nodesv1alpha1.NodeGroup{
307312
nodesv1alpha1.NodeGroupLight,
308313
nodesv1alpha1.NodeGroupMedium,
309314
nodesv1alpha1.NodeGroupHeavy,
310315
nodesv1alpha1.NodeGroupArchive,
311-
nodesv1alpha1.NodeGroupStorage,
312-
nodesv1alpha1.NodeGroupBlockchain,
313316
}
314317

315318
for _, chain := range allChains {
@@ -319,15 +322,16 @@ func TestAllAdaptersNodeSelector(t *testing.T) {
319322
if !ok {
320323
t.Fatalf("adapter not registered for chain: %s", chain)
321324
}
322-
for _, g := range groups {
325+
for _, g := range specialised {
323326
sel := adapter.NodeSelector(g)
324-
if sel == nil {
325-
t.Errorf("chain %s, group %s: nil node selector", chain, g)
326-
}
327327
if len(sel) == 0 {
328-
t.Errorf("chain %s, group %s: empty node selector", chain, g)
328+
t.Errorf("chain %s, group %s: expected non-empty selector for specialised group", chain, g)
329329
}
330330
}
331+
for _, g := range generic {
332+
// nil is acceptable — pods schedule on any node
333+
_ = adapter.NodeSelector(g)
334+
}
331335
})
332336
}
333337
}
@@ -761,16 +765,16 @@ func TestDefaultNodeSelectorKnownGroups(t *testing.T) {
761765
}
762766

763767
func TestDefaultNodeSelectorFallback(t *testing.T) {
764-
sel := adapters.DefaultNodeSelector(nodesv1alpha1.NodeGroupHeavy)
765-
if sel == nil {
766-
t.Fatal("nil selector")
767-
}
768-
val, exists := sel["node-type"]
769-
if !exists {
770-
t.Error("fallback should use 'node-type' key")
771-
}
772-
if val != string(nodesv1alpha1.NodeGroupHeavy) {
773-
t.Errorf("expected value %q, got %q", nodesv1alpha1.NodeGroupHeavy, val)
768+
// Generic groups (light/medium/heavy) return nil so pods schedule on any node.
769+
for _, g := range []nodesv1alpha1.NodeGroup{
770+
nodesv1alpha1.NodeGroupLight,
771+
nodesv1alpha1.NodeGroupMedium,
772+
nodesv1alpha1.NodeGroupHeavy,
773+
} {
774+
sel := adapters.DefaultNodeSelector(g)
775+
if sel != nil {
776+
t.Errorf("group %q: expected nil selector, got %v", g, sel)
777+
}
774778
}
775779
}
776780

internal/adapters/interface.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,16 @@ func MustGet(chain nodesv1alpha1.Chain) ChainAdapter {
165165
return a
166166
}
167167

168-
// DefaultNodeSelector returns a standard node selector based on node group label.
168+
// DefaultNodeSelector returns a node selector for specialised node groups only.
169+
// Generic groups (light/medium/heavy) return nil so pods schedule on any node.
169170
func DefaultNodeSelector(nodeGroup nodesv1alpha1.NodeGroup) map[string]string {
170171
switch nodeGroup {
171172
case nodesv1alpha1.NodeGroupStorage:
172173
return map[string]string{"workload-type": "storage"}
173174
case nodesv1alpha1.NodeGroupBlockchain:
174175
return map[string]string{"node-role.kubernetes.io/blockchain": "true"}
175176
default:
176-
return map[string]string{"node-type": string(nodeGroup)}
177+
return nil
177178
}
178179
}
179180

internal/controller/blockchainnode_controller_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,8 @@ var _ = Describe("BlockchainNode Controller", func() {
595595

596596
sts := &appsv1.StatefulSet{}
597597
Expect(k8sClient.Get(ctx, nn, sts)).To(Succeed())
598-
Expect(sts.Spec.Template.Spec.NodeSelector).To(HaveKeyWithValue("node-type", "heavy"))
598+
// Generic node groups return nil selector so pods schedule on any node.
599+
Expect(sts.Spec.Template.Spec.NodeSelector).To(BeEmpty())
599600
})
600601
})
601602

0 commit comments

Comments
 (0)