-
Notifications
You must be signed in to change notification settings - Fork 2
feat(api): spec.nodeConfig serves config.toml and app.toml verbatim from ConfigMaps #565
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
07e241f
3bb2054
53aa878
707e8d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,6 +58,15 @@ import ( | |
| // requests.memory, and the controller derives the limit from the request, so the | ||
| // footprint is frozen by freezing requests. | ||
| // +kubebuilder:validation:XValidation:rule="(!has(self.resources) && !has(oldSelf.resources)) || (has(self.resources) && has(oldSelf.resources) && (has(self.resources.requests) == has(oldSelf.resources.requests)) && (!has(self.resources.requests) || ((('cpu' in self.resources.requests) == ('cpu' in oldSelf.resources.requests)) && (('memory' in self.resources.requests) == ('memory' in oldSelf.resources.requests)) && (!('cpu' in self.resources.requests) || !('cpu' in oldSelf.resources.requests) || quantity(string(self.resources.requests['cpu'])).compareTo(quantity(string(oldSelf.resources.requests['cpu']))) == 0) && (!('memory' in self.resources.requests) || !('memory' in oldSelf.resources.requests) || quantity(string(self.resources.requests['memory'])).compareTo(quantity(string(oldSelf.resources.requests['memory']))) == 0))))",message="spec.resources is create-only: the footprint is fixed at creation (a change is not rolled onto a running pod — the StatefulSet is OnDelete and drift detection is image-only), so replace the node to resize" | ||
| // A node with spec.nodeConfig runs no task that writes config.toml or | ||
| // app.toml, so every field whose only route to seid was one of those tasks is | ||
| // rejected beside it. Accepting one would report success on an edit that never | ||
| // reached the node — and for peers, status.resolvedPeers would keep updating | ||
| // and keep looking correct while config.toml stayed as the operator wrote it. | ||
| // +kubebuilder:validation:XValidation:rule="!has(self.nodeConfig) || !has(self.configValues)",message="spec.nodeConfig and spec.configValues cannot both be set: a node reading its config from ConfigMaps runs no config-patch task, so the values would never reach seid; put them in the ConfigMap" | ||
| // +kubebuilder:validation:XValidation:rule="!has(self.nodeConfig) || !has(self.overrides)",message="spec.nodeConfig and spec.overrides cannot both be set: a node reading its config from ConfigMaps runs no config-apply task, so the overrides would never reach seid; put them in the ConfigMap" | ||
| // +kubebuilder:validation:XValidation:rule="!has(self.nodeConfig) || !has(self.peers)",message="spec.nodeConfig and spec.peers cannot both be set: nothing carries a resolved peer set into config.toml on a node reading its config from ConfigMaps; write p2p.persistent-peers in the ConfigMap" | ||
| // +kubebuilder:validation:XValidation:rule="!has(self.nodeConfig) || !has(self.externalAddress)",message="spec.nodeConfig and spec.externalAddress cannot both be set: nothing carries it into config.toml on a node reading its config from ConfigMaps; write p2p.external-address in the ConfigMap" | ||
| type SeiNodeSpec struct { | ||
| // ChainID of the chain this node belongs to. | ||
| // Constrained to DNS-1123 label characters because the controller composes | ||
|
|
@@ -122,6 +131,41 @@ type SeiNodeSpec struct { | |
| // +listMapKey=key | ||
| ConfigValues []ConfigValue `json:"configValues,omitempty"` | ||
|
|
||
| // NodeConfig supplies this node's seid config files from existing | ||
| // ConfigMaps. The files mount read-only over the seid config directory, so | ||
| // they replace whatever the data volume already holds. | ||
| // | ||
| // A node with this field set takes the static-config plan: no task in its | ||
| // plan writes either file on the production pod. That is what keeps the | ||
| // mount attached. A rename onto a mounted path from another container | ||
| // detaches the mount, and seid then reads the writer's file. | ||
| // | ||
| // The operator owns both files verbatim. The controller supplies nothing: | ||
| // not the mode's base configuration, not persistent-peers, not | ||
| // external-address, not the freeze height, not the snapshot-generation | ||
| // keys. It does not validate them either — replace-pod parses both files | ||
| // before it deletes a pod, and that is the only check. | ||
| // | ||
| // The fields whose only route to seid was a config task are rejected | ||
| // beside this one: configValues, overrides, peers, externalAddress. | ||
| // | ||
| // The references are the unit of change. Kubelet pins a subPath mount at | ||
| // pod start, so editing a ConfigMap in place does not reach a running pod. | ||
| // Publish under a new name and the node rolls. | ||
| // | ||
| // Not supported with a bootstrap Job, a state-sync snapshot source, a | ||
| // genesis ceremony, or consensus engine Autobahn. Each of those writes | ||
| // config.toml at run time, and the plan is refused. | ||
| // | ||
| // The StatefulSet carries the references as soon as they are set, before | ||
| // any plan runs. A reference that does not resolve leaves the template | ||
| // unmountable: the controller will not replace the pod itself, but a | ||
| // drain, an eviction, or a manual delete recreates it into | ||
| // ContainerCreating, and StatefulSets are OnDelete so nothing rolls it | ||
| // back. Create the ConfigMaps first. | ||
| // +optional | ||
| NodeConfig *NodeConfig `json:"nodeConfig,omitempty"` | ||
|
|
||
| // Scheduling configures worker-node isolation. | ||
| // +optional | ||
| Scheduling *SchedulingConfig `json:"scheduling,omitempty"` | ||
|
|
@@ -347,6 +391,32 @@ func (s *SeiNodeSpec) SnapshotSource() *SnapshotSource { | |
| } | ||
| } | ||
|
|
||
| // NodeConfig supplies a node's seid config files from existing ConfigMaps. | ||
| // Both references are required: a node that takes its config.toml from a | ||
| // ConfigMap and its app.toml from the controller would have two owners of one | ||
| // directory, and the controller's writer would detach the mount delivering the | ||
| // other file. | ||
| type NodeConfig struct { | ||
| // ConfigRef holds config.toml. | ||
| ConfigRef ConfigFileRef `json:"configRef"` | ||
|
|
||
| // AppRef holds app.toml. | ||
| AppRef ConfigFileRef `json:"appRef"` | ||
| } | ||
|
|
||
| // ConfigFileRef names the ConfigMap holding one seid config file. Both | ||
| // references may name the same ConfigMap. | ||
| type ConfigFileRef struct { | ||
| // Name of an existing ConfigMap in the SeiNode's namespace. The file is | ||
| // read from the key matching its own name, config.toml or app.toml. | ||
| // Kubelet refuses the mount when that key is absent, so the pod stays in | ||
| // ContainerCreating and `kubectl describe pod` names the missing key. | ||
| // +kubebuilder:validation:MinLength=1 | ||
| // +kubebuilder:validation:MaxLength=253 | ||
| // +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` | ||
| Name string `json:"name"` | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion — Name carries only length bounds, so a value that is not a DNS subdomain passes admission and then renders a StatefulSet the API server rejects, leaving apply-statefulset failing every reconcile. The sibling object-name fields in this file (DataVolumeImport.PVCName, volumeAttributesClassName) both pin the DNS pattern; adding it here rejects the typo at
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 707e8d1 — pinned to the same DNS-subdomain pattern the sibling object-name fields use. The field carried a pattern before the rename to configRef/appRef and I dropped it in the move. |
||
| } | ||
|
|
||
| // NodeKeySecret returns the Secret supplying this node's P2P identity | ||
| // (node_key.json), or nil when the node has none and `seid init` generates one | ||
| // onto the data volume. | ||
|
|
@@ -486,6 +556,14 @@ type TaskPlan struct { | |
| // +optional | ||
| ConfigValuesHash string `json:"configValuesHash,omitempty"` | ||
|
|
||
| // ClearsNodeConfig marks a plan that takes the operator's ConfigMaps away | ||
| // from a node. On successful completion Status.CurrentNodeConfig is | ||
| // cleared. It is not cleared earlier: the plan writes the | ||
| // controller-managed base after the pod is replaced, and until that write | ||
| // lands the stamp is what keeps the node on the planner that will retry. | ||
| // +optional | ||
| ClearsNodeConfig bool `json:"clearsNodeConfig,omitempty"` | ||
|
|
||
| // FailedPhase is the SeiNodePhase the executor sets on the owning | ||
| // resource when the plan fails terminally. When empty, the executor | ||
| // does not perform a phase transition on failure. | ||
|
|
@@ -781,6 +859,16 @@ type SeiNodeStatus struct { | |
| // +optional | ||
| CurrentNodeIsolation NodeIsolation `json:"currentNodeIsolation,omitempty"` | ||
|
|
||
| // CurrentNodeConfig is the spec.nodeConfig the owned StatefulSet's pod was | ||
| // last rolled with, stamped jointly with CurrentImage on rollout | ||
| // completion. Unset means the pod mounts no operator-supplied config. | ||
| // | ||
| // Unset means the pod mounts no operator-supplied config. Unlike the fields | ||
| // above, unset is a real observation and not "not yet observed". It records | ||
| // the references, never the ConfigMaps' contents. | ||
| // +optional | ||
| CurrentNodeConfig *NodeConfig `json:"currentNodeConfig,omitempty"` | ||
|
|
||
| // +listType=map | ||
| // +listMapKey=type | ||
| // +optional | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion — Name carries only length bounds, so a value that is not a DNS subdomain passes admission and then renders a StatefulSet the API server rejects, leaving apply-statefulset failing every reconcile. The sibling object-name fields in this file (DataVolumeImport.PVCName, volumeAttributesClassName) both pin the DNS pattern; adding it here rejects the typo at
kubectl apply. (Raised by codex and confirmed.)