From d042e8e1a1e47fd57831ea12ee088f79c448c881 Mon Sep 17 00:00:00 2001 From: Christophe Varoqui Date: Thu, 19 Feb 2026 11:01:03 +0100 Subject: [PATCH 1/6] Move the labels from node.Status to node.Config * drop useless node.Node.Labels() * log san paths and labels changes * don't log icfg refreshs at info level (flooding logs) --- core/commoncmd/text/node-events/flag/filter | 3 +- core/node/config.go | 8 + core/node/status.go | 3 - core/object/node_config.go | 4 - daemon/api/api.yaml | 8 +- daemon/api/codegen_server_gen.go | 380 ++++++++++---------- daemon/api/codegen_type_gen.go | 22 +- daemon/api/unstructured.go | 2 +- daemon/daemonapi/get_node.go | 8 +- daemon/daemondata/apply_full.go | 24 +- daemon/daemondata/apply_patch.go | 2 +- daemon/daemondata/data.go | 4 +- daemon/daemondata/data_init.go | 2 +- daemon/icfg/main.go | 16 +- daemon/msgbus/messages.go | 20 +- daemon/nmon/main.go | 47 +-- daemon/nmon/main_cmd.go | 1 + util/san/main.go | 39 +- util/xmap/main.go | 31 +- 19 files changed, 352 insertions(+), 272 deletions(-) diff --git a/core/commoncmd/text/node-events/flag/filter b/core/commoncmd/text/node-events/flag/filter index 3a98a7235..6de86ed2b 100644 --- a/core/commoncmd/text/node-events/flag/filter +++ b/core/commoncmd/text/node-events/flag/filter @@ -64,13 +64,14 @@ Receive only events matching the filtering expression formatted as: NodeAlive, NodeStale NodeConfigUpdated, NodeDataUpdated, NodeFrozen NodeFrozenFileRemoved, NodeFrozenFileUpdated + NodeLabelsUpdated, NodeLabelsCommited NodeMonitorDeleted, NodeMonitorUpdated NodeOsPathsUpdated NodePoolStatusUpdated, NodePoolStatusDeleted NodeRejoin, NodeSplitAction NodeStatsUpdated NodeStatusUpdated - NodeStatusArbitratorsUpdated, NodeStatusGenUpdates, NodeStatusLabelsUpdated + NodeStatusArbitratorsUpdated, NodeStatusGenUpdates, SetNodeMonitor ### Object diff --git a/core/node/config.go b/core/node/config.go index 08fa52601..18f9c0c3f 100644 --- a/core/node/config.go +++ b/core/node/config.go @@ -1,14 +1,17 @@ package node import ( + "maps" "time" "github.com/opensvc/om3/v3/core/schedule" + "github.com/opensvc/om3/v3/util/label" ) type ( Config struct { Env string `json:"env"` + Labels label.M `json:"labels"` MaintenanceGracePeriod time.Duration `json:"maintenance_grace_period"` MaxParallel int `json:"max_parallel"` MaxKeySize int64 `json:"max_key_size"` @@ -26,6 +29,7 @@ type ( func (cfg *Config) DeepCopy() *Config { newCfg := *cfg newCfg.Schedules = append([]schedule.Config{}, cfg.Schedules...) + newCfg.Labels = cfg.Labels.DeepCopy() return &newCfg } @@ -45,6 +49,10 @@ func (c Config) Equals(other Config) bool { return false } + if !maps.Equal(c.Labels, other.Labels) { + return false + } + // Compare Schedules slice if len(c.Schedules) != len(other.Schedules) { return false diff --git a/core/node/status.go b/core/node/status.go index 34a596202..fdb2178d3 100644 --- a/core/node/status.go +++ b/core/node/status.go @@ -5,7 +5,6 @@ import ( "github.com/opensvc/om3/v3/core/instance" "github.com/opensvc/om3/v3/core/status" - "github.com/opensvc/om3/v3/util/label" ) type ( @@ -18,7 +17,6 @@ type ( Gen Gen `json:"gen"` IsLeader bool `json:"is_leader"` IsOverloaded bool `json:"is_overloaded"` - Labels label.M `json:"labels"` BootedAt time.Time `json:"booted_at"` } @@ -59,7 +57,6 @@ func (t *Status) DeepCopy() *Status { newGen[n] = v } result.Gen = newGen - result.Labels = t.Labels.DeepCopy() return &result } diff --git a/core/object/node_config.go b/core/object/node_config.go index 147b4594c..879255f9a 100644 --- a/core/object/node_config.go +++ b/core/object/node_config.go @@ -176,7 +176,3 @@ func (t *Node) CNIPlugins() (string, error) { return s.(string), nil } } - -func (t *Node) Labels() map[string]string { - return t.config.SectionMap("labels") -} diff --git a/daemon/api/api.yaml b/daemon/api/api.yaml index bd8b3e1e8..7be9fec63 100644 --- a/daemon/api/api.yaml +++ b/daemon/api/api.yaml @@ -6077,6 +6077,7 @@ components: type: object required: - env + - labels - maintenance_grace_period - max_parallel - min_avail_mem_pct @@ -6089,6 +6090,9 @@ components: properties: env: type: string + labels: + additionalProperties: + type: string maintenance_grace_period: x-go-type: time.Duration max_parallel: @@ -6243,7 +6247,6 @@ components: - gen - is_leader - is_overloaded - - labels properties: agent: type: string @@ -6268,9 +6271,6 @@ components: type: boolean is_overloaded: type: boolean - labels: - additionalProperties: - type: string NodesInfo: x-go-type: nodesinfo.M diff --git a/daemon/api/codegen_server_gen.go b/daemon/api/codegen_server_gen.go index 73685a19f..a65b49cc4 100644 --- a/daemon/api/codegen_server_gen.go +++ b/daemon/api/codegen_server_gen.go @@ -6315,196 +6315,196 @@ var swaggerSpec = []string{ "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", - "88cl8Cxpfby5ubn5PwEAAP//G2JFs459AgA=", + "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", + "As+S1sebm5ub/xMAAP//7deHJY59AgA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/api/codegen_type_gen.go b/daemon/api/codegen_type_gen.go index 1f00365a5..e4d0eae26 100644 --- a/daemon/api/codegen_type_gen.go +++ b/daemon/api/codegen_type_gen.go @@ -847,16 +847,17 @@ type NodeActionAccepted struct { // NodeConfig defines model for NodeConfig. type NodeConfig struct { - Env string `json:"env"` - MaintenanceGracePeriod time.Duration `json:"maintenance_grace_period"` - MaxParallel int `json:"max_parallel"` - MinAvailMemPct int `json:"min_avail_mem_pct"` - MinAvailSwapPct int `json:"min_avail_swap_pct"` - PRKey string `json:"prkey"` - ReadyPeriod time.Duration `json:"ready_period"` - RejoinGracePeriod time.Duration `json:"rejoin_grace_period"` - SplitAction string `json:"split_action"` - SSHKey string `json:"sshkey"` + Env string `json:"env"` + Labels map[string]string `json:"labels"` + MaintenanceGracePeriod time.Duration `json:"maintenance_grace_period"` + MaxParallel int `json:"max_parallel"` + MinAvailMemPct int `json:"min_avail_mem_pct"` + MinAvailSwapPct int `json:"min_avail_swap_pct"` + PRKey string `json:"prkey"` + ReadyPeriod time.Duration `json:"ready_period"` + RejoinGracePeriod time.Duration `json:"rejoin_grace_period"` + SplitAction string `json:"split_action"` + SSHKey string `json:"sshkey"` } // NodeInfo defines model for NodeInfo. @@ -932,7 +933,6 @@ type NodeStatus struct { Gen map[string]uint64 `json:"gen"` IsLeader bool `json:"is_leader"` IsOverloaded bool `json:"is_overloaded"` - Labels map[string]string `json:"labels"` } // NodesInfo defines model for NodesInfo. diff --git a/daemon/api/unstructured.go b/daemon/api/unstructured.go index 271cfc4c3..90039f2cb 100644 --- a/daemon/api/unstructured.go +++ b/daemon/api/unstructured.go @@ -118,6 +118,7 @@ func (t Node) Unstructured() map[string]any { func (t *NodeConfig) Unstructured() map[string]any { return map[string]any{ "env": t.Env, + "labels": t.Labels, "maintenance_grace_period": t.MaintenanceGracePeriod, "min_avail_mem_pct": t.MinAvailMemPct, "min_avail_swap_pct": t.MinAvailSwapPct, @@ -140,7 +141,6 @@ func (t *NodeStatus) Unstructured() map[string]any { "gen": t.Gen, "is_leader": t.IsLeader, "is_overloaded": t.IsOverloaded, - "labels": t.Labels, } } diff --git a/daemon/daemonapi/get_node.go b/daemon/daemonapi/get_node.go index 757039bd3..4ba4617ac 100644 --- a/daemon/daemonapi/get_node.go +++ b/daemon/daemonapi/get_node.go @@ -42,6 +42,7 @@ func (a *DaemonAPI) GetNodes(ctx echo.Context, params api.GetNodesParams) error if config.Value != nil { d.Data.Config = &api.NodeConfig{ Env: config.Value.Env, + Labels: make(map[string]string), MaintenanceGracePeriod: config.Value.MaintenanceGracePeriod, MaxParallel: config.Value.MaxParallel, MinAvailMemPct: config.Value.MinAvailMemPct, @@ -52,6 +53,9 @@ func (a *DaemonAPI) GetNodes(ctx echo.Context, params api.GetNodesParams) error RejoinGracePeriod: config.Value.RejoinGracePeriod, SplitAction: config.Value.SplitAction, } + for k, v := range config.Value.Labels { + d.Data.Config.Labels[k] = v + } } if status != nil { d.Data.Status = &api.NodeStatus{ @@ -63,7 +67,6 @@ func (a *DaemonAPI) GetNodes(ctx echo.Context, params api.GetNodesParams) error Gen: make(map[string]uint64), IsLeader: status.IsLeader, IsOverloaded: status.IsOverloaded, - Labels: make(map[string]string), } for k, v := range status.Arbitrators { d.Data.Status.Arbitrators[k] = api.ArbitratorStatus{ @@ -75,9 +78,6 @@ func (a *DaemonAPI) GetNodes(ctx echo.Context, params api.GetNodesParams) error for k, v := range status.Gen { d.Data.Status.Gen[k] = v } - for k, v := range status.Labels { - d.Data.Status.Labels[k] = v - } } if monitor != nil { d.Data.Monitor = &api.NodeMonitor{ diff --git a/daemon/daemondata/apply_full.go b/daemon/daemondata/apply_full.go index ec492eca4..41ae33379 100644 --- a/daemon/daemondata/apply_full.go +++ b/daemon/daemondata/apply_full.go @@ -113,17 +113,31 @@ func (d *data) pubMsgFromNodeConfigDiffForNode(peer string) { prevTime, hasPrev = d.previousRemoteInfo[peer] prev = prevTime.nodeConfig onUpdate := func() { - if !reflect.DeepEqual(prev, next) { + var changed bool + if !reflect.DeepEqual(prev.Labels, next.Labels) { + d.publisher.Pub(&msgbus.NodeLabelsUpdated{Node: peer, Value: next.Labels.DeepCopy()}, + pubsub.Label{"node", peer}, + labelFromPeer, + ) + changed = true + } + if changed || !reflect.DeepEqual(prev, next) { node.ConfigData.Set(peer, next.DeepCopy()) d.publisher.Pub(&msgbus.NodeConfigUpdated{Node: peer, Value: *next.DeepCopy()}, pubsub.Label{"node", peer}, + // why not labelFromPeer ? ) } } onCreate := func() { + d.publisher.Pub(&msgbus.NodeLabelsUpdated{Node: peer, Value: next.Labels.DeepCopy()}, + pubsub.Label{"node", peer}, + labelFromPeer, + ) node.ConfigData.Set(peer, next.DeepCopy()) d.publisher.Pub(&msgbus.NodeConfigUpdated{Node: peer, Value: *next.DeepCopy()}, pubsub.Label{"node", peer}, + // why not labelFromPeer ? ) } @@ -189,18 +203,12 @@ func (d *data) pubMsgFromNodeStatusDiffForNode(peer string) { labelFromPeer, } onUpdate := func() { - var changed bool - if !reflect.DeepEqual(prev.Labels, next.Labels) { - d.publisher.Pub(&msgbus.NodeStatusLabelsUpdated{Node: peer, Value: next.Labels.DeepCopy()}, labels...) - changed = true - } - if changed || !reflect.DeepEqual(prev, next) { + if !reflect.DeepEqual(prev, next) { node.StatusData.Set(peer, next.DeepCopy()) d.publisher.Pub(&msgbus.NodeStatusUpdated{Node: peer, Value: *next.DeepCopy()}, labels...) } } onCreate := func() { - d.publisher.Pub(&msgbus.NodeStatusLabelsUpdated{Node: peer, Value: next.Labels.DeepCopy()}, labels...) node.StatusData.Set(peer, next.DeepCopy()) d.publisher.Pub(&msgbus.NodeStatusUpdated{Node: peer, Value: *next.DeepCopy()}, labels...) } diff --git a/daemon/daemondata/apply_patch.go b/daemon/daemondata/apply_patch.go index 67e77b015..60e47a601 100644 --- a/daemon/daemondata/apply_patch.go +++ b/daemon/daemondata/apply_patch.go @@ -201,7 +201,7 @@ func (d *data) setCacheAndPublish(ev event.Event) error { case *msgbus.NodeStatsUpdated: node.StatsData.Set(c.Node, &c.Value) d.publisher.Pub(c, labelFromPeer) - case *msgbus.NodeStatusLabelsUpdated: + case *msgbus.NodeLabelsUpdated: d.publisher.Pub(c, labelFromPeer) case *msgbus.NodeStatusUpdated: node.StatusData.Set(c.Node, &c.Value) diff --git a/daemon/daemondata/data.go b/daemon/daemondata/data.go index 9cd69d179..4b379ab39 100644 --- a/daemon/daemondata/data.go +++ b/daemon/daemondata/data.go @@ -430,7 +430,7 @@ func (d *data) startSubscriptions(ctx context.Context, qs pubsub.QueueSizer) { sub.AddFilter(&msgbus.NodeOsPathsUpdated{}, d.labelLocalhost) sub.AddFilter(&msgbus.NodeStatsUpdated{}, d.labelLocalhost) sub.AddFilter(&msgbus.NodeStatusUpdated{}, d.labelLocalhost) - sub.AddFilter(&msgbus.NodeStatusLabelsUpdated{}, d.labelLocalhost) + sub.AddFilter(&msgbus.NodeLabelsUpdated{}, d.labelLocalhost) // need forward to peers sub.AddFilter(&msgbus.ObjectCreated{}, d.labelLocalhost) @@ -499,7 +499,7 @@ func localEventMustBeForwarded(i interface{}) bool { case *msgbus.NodeOsPathsUpdated: case *msgbus.NodeStatsUpdated: case *msgbus.NodeStatusUpdated: - case *msgbus.NodeStatusLabelsUpdated: + case *msgbus.NodeLabelsUpdated: // object... case *msgbus.ObjectCreated: diff --git a/daemon/daemondata/data_init.go b/daemon/daemondata/data_init.go index 7d390b7af..de11fe06a 100644 --- a/daemon/daemondata/data_init.go +++ b/daemon/daemondata/data_init.go @@ -83,6 +83,7 @@ func newNodeData(localNode string) node.Node { nodeStatus := node.Node{ Config: node.Config{ // use initial default value + Labels: label.M{}, MaxParallel: object.DefaultNodeMaxParallel, MinAvailMemPct: 0, MinAvailSwapPct: 0, @@ -103,7 +104,6 @@ func newNodeData(localNode string) node.Node { Compat: 12, FrozenAt: frozen, Gen: node.Gen{localNode: 1}, - Labels: label.M{}, }, Os: node.Os{ Paths: san.Paths{}, diff --git a/daemon/icfg/main.go b/daemon/icfg/main.go index 087c7ccb2..feed02ba3 100644 --- a/daemon/icfg/main.go +++ b/daemon/icfg/main.go @@ -159,7 +159,7 @@ func (t *Manager) startSubscriptions() { // the scope value may depend on cluster nodes values: *, clusternodes ... // so we must also watch for cluster config updates to configFileCheckRefresh non cluster instance config scope t.sub.AddFilter(&msgbus.InstanceConfigUpdated{}, labelPathCluster, labelLocalhost) - t.sub.AddFilter(&msgbus.NodeStatusLabelsCommited{}) + t.sub.AddFilter(&msgbus.NodeLabelsCommited{}) } else { // Special note for cluster instance config: we don't subscribe for ConfigFileUpdated, instead we subscribe for // ClusterConfigUpdated. @@ -199,8 +199,8 @@ func (t *Manager) worker() { t.onConfigFileUpdated() case *msgbus.InstanceConfigUpdated: t.onLocalClusterInstanceConfigUpdated() - case *msgbus.NodeStatusLabelsCommited: - t.onNodeStatusLabelsCommited() + case *msgbus.NodeLabelsCommited: + t.onNodeLabelsCommited() } } } @@ -219,22 +219,22 @@ func (t *Manager) configFileCheckRefresh(force bool) error { } func (t *Manager) onClusterConfigUpdated() { - t.log.Infof("cluster config updated => refresh") + t.log.Tracef("cluster config updated => refresh") _ = t.configFileCheckRefresh(true) } func (t *Manager) onConfigFileUpdated() { - t.log.Infof("config file updated => refresh if csum changed") + t.log.Tracef("config file updated => refresh if csum changed") _ = t.configFileCheckRefresh(false) } func (t *Manager) onLocalClusterInstanceConfigUpdated() { - t.log.Infof("cluster instance config changed => refresh") + t.log.Tracef("cluster instance config changed => refresh") _ = t.configFileCheckRefresh(true) } -func (t *Manager) onNodeStatusLabelsCommited() { - t.log.Infof("node labels changed => refresh") +func (t *Manager) onNodeLabelsCommited() { + t.log.Tracef("node labels changed => refresh") _ = t.configFileCheckRefresh(true) } diff --git a/daemon/msgbus/messages.go b/daemon/msgbus/messages.go index 07d84c356..f400c981d 100644 --- a/daemon/msgbus/messages.go +++ b/daemon/msgbus/messages.go @@ -208,9 +208,9 @@ var ( "NodeStatusGenUpdates": func() any { return &NodeStatusGenUpdates{} }, - "NodeStatusLabelsCommited": func() any { return &NodeStatusLabelsCommited{} }, + "NodeLabelsCommited": func() any { return &NodeLabelsCommited{} }, - "NodeStatusLabelsUpdated": func() any { return &NodeStatusLabelsUpdated{} }, + "NodeLabelsUpdated": func() any { return &NodeLabelsUpdated{} }, "NodeSplitAction": func() any { return &NodeSplitAction{} }, @@ -775,13 +775,13 @@ type ( Value node.Gen `json:"gens" yaml:"gens"` } - NodeStatusLabelsCommited struct { + NodeLabelsCommited struct { pubsub.Msg `yaml:",inline"` Node string `json:"node" yaml:"node"` Value label.M `json:"node_labels" yaml:"node_labels"` } - NodeStatusLabelsUpdated struct { + NodeLabelsUpdated struct { pubsub.Msg `yaml:",inline"` Node string `json:"node" yaml:"node"` Value label.M `json:"node_labels" yaml:"node_labels"` @@ -1329,16 +1329,16 @@ func (e *NodeStatusGenUpdates) Key() string { return fmt.Sprintf("NodeStatusGenUpdates,node=%s", e.Node) } -func (e *NodeStatusLabelsCommited) Kind() string { - return "NodeStatusLabelsCommited" +func (e *NodeLabelsCommited) Kind() string { + return "NodeLabelsCommited" } -func (e *NodeStatusLabelsUpdated) Kind() string { - return "NodeStatusLabelsUpdated" +func (e *NodeLabelsUpdated) Kind() string { + return "NodeLabelsUpdated" } -func (e *NodeStatusLabelsUpdated) Key() string { - return fmt.Sprintf("NodeStatusLabelsUpdated,node=%s", e.Node) +func (e *NodeLabelsUpdated) Key() string { + return fmt.Sprintf("NodeLabelsUpdated,node=%s", e.Node) } func (e *NodeStale) Kind() string { diff --git a/daemon/nmon/main.go b/daemon/nmon/main.go index 5d23b0864..b1afd972f 100644 --- a/daemon/nmon/main.go +++ b/daemon/nmon/main.go @@ -30,7 +30,6 @@ import ( "os" "path/filepath" "runtime" - "slices" "sync" "time" @@ -59,6 +58,7 @@ import ( "github.com/opensvc/om3/v3/util/pubsub" "github.com/opensvc/om3/v3/util/san" "github.com/opensvc/om3/v3/util/version" + "github.com/opensvc/om3/v3/util/xmap" ) type ( @@ -118,7 +118,7 @@ type ( // cacheNodesInfo is a map of nodes to node.NodeInfo, it is used to // maintain the nodes_info.json file. // local values are computed by nmon. - // peer values are updated from msgbus events NodeStatusLabelsUpdated, NodeConfigUpdated, NodeOsPathsUpdated + // peer values are updated from msgbus events NodeLabelsUpdated, NodeConfigUpdated, NodeOsPathsUpdated // and ForgetPeer. cacheNodesInfo nodesinfo.M @@ -346,7 +346,7 @@ func (t *Manager) startSubscriptions() { sub.AddFilter(&msgbus.NodeOsPathsUpdated{}, pubsub.Label{"from", "peer"}) sub.AddFilter(&msgbus.NodeRejoin{}, t.labelLocalhost) sub.AddFilter(&msgbus.NodeStatusGenUpdates{}, t.labelLocalhost) - sub.AddFilter(&msgbus.NodeStatusLabelsUpdated{}, pubsub.Label{"from", "peer"}) + sub.AddFilter(&msgbus.NodeLabelsUpdated{}, pubsub.Label{"from", "peer"}) sub.AddFilter(&msgbus.SetNodeMonitor{}) sub.Start() t.sub = sub @@ -468,8 +468,8 @@ func (t *Manager) worker() { t.onNodeFrozenFileRemoved(c) case *msgbus.NodeFrozenFileUpdated: t.onNodeFrozenFileUpdated(c) - case *msgbus.NodeStatusLabelsUpdated: - t.onPeerNodeStatusLabelsUpdated(c) + case *msgbus.NodeLabelsUpdated: + t.onPeerNodeLabelsUpdated(c) case *msgbus.NodeStatusGenUpdates: t.onNodeStatusGenUpdates(c) case *msgbus.LeaveRequest: @@ -706,14 +706,14 @@ func (t *Manager) onPeerNodeOsPathsUpdated(m *msgbus.NodeOsPathsUpdated) { t.saveNodesInfo() } -func (t *Manager) onPeerNodeStatusLabelsUpdated(m *msgbus.NodeStatusLabelsUpdated) { +func (t *Manager) onPeerNodeLabelsUpdated(m *msgbus.NodeLabelsUpdated) { peerNodeInfo := t.cacheNodesInfo[m.Node] changed := !maps.Equal(peerNodeInfo.Labels, m.Value) peerNodeInfo.Labels = m.Value t.cacheNodesInfo[m.Node] = peerNodeInfo t.saveNodesInfo() if changed { - t.publisher.Pub(&msgbus.NodeStatusLabelsCommited{Node: m.Node, Value: m.Value.DeepCopy()}, t.labelLocalhost) + t.publisher.Pub(&msgbus.NodeLabelsCommited{Node: m.Node, Value: m.Value.DeepCopy()}, t.labelLocalhost) } } @@ -733,7 +733,7 @@ func (t *Manager) saveNodesInfo() { if err := nodesinfo.Save(t.cacheNodesInfo); err != nil { t.log.Errorf("save nodes info: %s", err) } else { - t.log.Infof("nodes info cache refreshed %s", t.cacheNodesInfo.Keys()) + t.log.Infof("saved nodes info %s", t.cacheNodesInfo.Keys()) } } @@ -768,10 +768,10 @@ func (t *Manager) loadConfig() error { if err != nil { return err } - localNodeInfo := t.cacheNodesInfo[t.localhost] - localNodeInfo.Labels = n.Labels() t.config = n.MergedConfig() t.nodeConfig = t.getNodeConfig() + localNodeInfo := t.cacheNodesInfo[t.localhost] + localNodeInfo.Labels = t.nodeConfig.Labels localNodeInfo.Env = t.nodeConfig.Env if lsnr := daemonsubsystem.DataListener.Get(t.localhost); lsnr != nil { @@ -788,40 +788,43 @@ func (t *Manager) loadConfigAndPublish() error { return err } - if !prevNodeConfig.Equals(t.nodeConfig) { - node.ConfigData.Set(t.localhost, t.nodeConfig.DeepCopy()) - t.publisher.Pub(&msgbus.NodeConfigUpdated{Node: t.localhost, Value: t.nodeConfig}, t.labelLocalhost) - } - if stats := node.StatsData.GetByNode(t.localhost); stats != nil && stats.MemTotalMB != 0 { t.updateIsOverloaded(*stats) } var labelsChanged, pathsChanged bool - localNodeInfo := t.cacheNodesInfo[t.localhost] - if !maps.Equal(localNodeInfo.Labels, t.nodeStatus.Labels) { - t.nodeStatus.Labels = localNodeInfo.Labels + + if diff := xmap.Diff(prevNodeConfig.Labels, t.nodeConfig.Labels); diff != "" { labelsChanged = true + t.log.Infof("labels changed: %s", diff) } paths := node.OsPathsData.GetByNode(t.localhost) - if paths == nil || !slices.Equal(localNodeInfo.Paths, *paths) { + if paths == nil { + paths = &san.Paths{} + } + if diff := paths.Diff(localNodeInfo.Paths); diff != "" { node.OsPathsData.Set(t.localhost, localNodeInfo.Paths.DeepCopy()) pathsChanged = true + t.log.Infof("paths changed: %s", diff) } if labelsChanged || pathsChanged { t.saveNodesInfo() } + if !prevNodeConfig.Equals(t.nodeConfig) { + node.ConfigData.Set(t.localhost, t.nodeConfig.DeepCopy()) + t.publisher.Pub(&msgbus.NodeConfigUpdated{Node: t.localhost, Value: t.nodeConfig}, t.labelLocalhost) + } + if labelsChanged { - t.publisher.Pub(&msgbus.NodeStatusLabelsUpdated{Node: t.localhost, Value: localNodeInfo.Labels.DeepCopy()}, t.labelLocalhost) - t.publisher.Pub(&msgbus.NodeStatusLabelsCommited{Node: t.localhost, Value: localNodeInfo.Labels.DeepCopy()}, t.labelLocalhost) + t.publisher.Pub(&msgbus.NodeLabelsUpdated{Node: t.localhost, Value: localNodeInfo.Labels.DeepCopy()}, t.labelLocalhost) + t.publisher.Pub(&msgbus.NodeLabelsCommited{Node: t.localhost, Value: localNodeInfo.Labels.DeepCopy()}, t.labelLocalhost) } if pathsChanged { t.publisher.Pub(&msgbus.NodeOsPathsUpdated{Node: t.localhost, Value: *localNodeInfo.Paths.DeepCopy()}, t.labelLocalhost) } t.updateSpeaker() - t.publishNodeStatus() select { case t.poolC <- nil: diff --git a/daemon/nmon/main_cmd.go b/daemon/nmon/main_cmd.go index 46c70a80c..2e3072ae8 100644 --- a/daemon/nmon/main_cmd.go +++ b/daemon/nmon/main_cmd.go @@ -82,6 +82,7 @@ func (t *Manager) getNodeConfig() node.Config { keyMinAvailSwapPct = key.New("node", "min_avail_swap_pct") ) cfg := node.Config{} + cfg.Labels = t.config.SectionMap("labels") if d := t.config.GetDuration(keyMaintenanceGracePeriod); d != nil { cfg.MaintenanceGracePeriod = *d } diff --git a/util/san/main.go b/util/san/main.go index 71c7effe9..fde1d24be 100644 --- a/util/san/main.go +++ b/util/san/main.go @@ -34,6 +34,39 @@ type ( } ) +func (t Paths) Diff(other Paths) string { + oldSet := make(map[string]any) + newSet := make(map[string]any) + + for _, item := range t { + oldSet[item.String()] = nil + } + for _, item := range other { + newSet[item.String()] = nil + } + + var changes []string + + // Check for added + for s := range newSet { + if _, exists := oldSet[s]; !exists { + changes = append(changes, fmt.Sprintf("+%s", s)) + } + } + + // Check for removed + for s := range oldSet { + if _, exists := newSet[s]; !exists { + changes = append(changes, fmt.Sprintf("-%s", s)) + } + } + + if len(changes) == 0 { + return "" + } + return strings.Join(changes, ", ") +} + func (t Paths) Mapping() string { return strings.Join(t.MappingList(), ",") } @@ -41,7 +74,7 @@ func (t Paths) Mapping() string { func (t Paths) MappingList() []string { l := make([]string, 0) for _, p := range t { - s := p.Initiator.Name + ":" + p.Target.Name + s := p.String() l = append(l, s) } return l @@ -210,6 +243,10 @@ func (t Paths) DeepCopy() *Paths { return &l } +func (t Path) String() string { + return fmt.Sprintf("%s:%s", t.Target.Name, t.Initiator.Name) +} + func (t Path) DeepCopy() Path { return Path{ Initiator: t.Initiator.DeepCopy(), diff --git a/util/xmap/main.go b/util/xmap/main.go index ba9cddc08..a7169db30 100644 --- a/util/xmap/main.go +++ b/util/xmap/main.go @@ -1,6 +1,10 @@ package xmap -import "reflect" +import ( + "fmt" + "reflect" + "strings" +) // Keys returns the slice of a map string keys. func Keys(i interface{}) []string { @@ -19,3 +23,28 @@ func Copy[K, V comparable](m map[K]V) map[K]V { } return result } + +func Diff(old, new map[string]string) string { + var changes []string + + // Check for added and changed + for k, newV := range new { + if oldV, exists := old[k]; !exists { + changes = append(changes, fmt.Sprintf("+%s=%s", k, newV)) + } else if oldV != newV { + changes = append(changes, fmt.Sprintf("~%s=%s->%s", k, oldV, newV)) + } + } + + // Check for removed + for k, oldV := range old { + if _, exists := new[k]; !exists { + changes = append(changes, fmt.Sprintf("-%s=%s", k, oldV)) + } + } + + if len(changes) == 0 { + return "" + } + return strings.Join(changes, ", ") +} From 690918273e26a33b8e46d633813981ea0d479442 Mon Sep 17 00:00:00 2001 From: Christophe Varoqui Date: Thu, 19 Feb 2026 12:47:14 +0100 Subject: [PATCH 2/6] Add the from=peer label to NodeConfigUpdated events --- daemon/daemondata/apply_full.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/daemondata/apply_full.go b/daemon/daemondata/apply_full.go index 41ae33379..0e5635a1b 100644 --- a/daemon/daemondata/apply_full.go +++ b/daemon/daemondata/apply_full.go @@ -125,7 +125,7 @@ func (d *data) pubMsgFromNodeConfigDiffForNode(peer string) { node.ConfigData.Set(peer, next.DeepCopy()) d.publisher.Pub(&msgbus.NodeConfigUpdated{Node: peer, Value: *next.DeepCopy()}, pubsub.Label{"node", peer}, - // why not labelFromPeer ? + labelFromPeer, ) } } @@ -137,7 +137,7 @@ func (d *data) pubMsgFromNodeConfigDiffForNode(peer string) { node.ConfigData.Set(peer, next.DeepCopy()) d.publisher.Pub(&msgbus.NodeConfigUpdated{Node: peer, Value: *next.DeepCopy()}, pubsub.Label{"node", peer}, - // why not labelFromPeer ? + labelFromPeer, ) } From bda7067959b9f0fb24fc666a1974df4505db9d96 Mon Sep 17 00:00:00 2001 From: Christophe Varoqui Date: Fri, 20 Feb 2026 08:19:00 +0100 Subject: [PATCH 3/6] Fix the go test broken by the node.Status.Labels move --- core/node/status_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/node/status_test.go b/core/node/status_test.go index 35200f833..6519d3cf2 100644 --- a/core/node/status_test.go +++ b/core/node/status_test.go @@ -28,10 +28,6 @@ func Test_TStatus_DeepCopy(t *testing.T) { }, IsOverloaded: false, IsLeader: true, - Labels: map[string]string{ - "node1": "abc", - "node2": "efg", - }, } copyValue := value.DeepCopy() From 920ba55c049f452236746ec5d6abd56830cf1744 Mon Sep 17 00:00:00 2001 From: Christophe Varoqui Date: Fri, 20 Feb 2026 11:30:51 +0100 Subject: [PATCH 4/6] Fix event hooks * Add the hooks to node.Config * Detect the hooks config change when decide to fire a NodeConfigUpdated event * Move output.Flatten to flatten.Flatten as the data flattening is used for more than the output renderer (e.g. in logs) * Rename node.Config.Equals to node.Config.Equal (aligned with Equal functions in the golang core) Example: $ bin/om cluster config show --section hook#1 [hook#1] events = HeartbeatAlive HeartbeatStale command = env | tee /tmp/hook1.txt $ bin/om daemon status -o flat | grep hook cluster.node.dev2n1.config.hooks[0].command[0] = "env" cluster.node.dev2n1.config.hooks[0].command[1] = "|" cluster.node.dev2n1.config.hooks[0].command[2] = "tee" cluster.node.dev2n1.config.hooks[0].command[3] = "/tmp/hook1.txt" cluster.node.dev2n1.config.hooks[0].events[0] = "HeartbeatAlive" cluster.node.dev2n1.config.hooks[0].events[1] = "HeartbeatStale" cluster.node.dev2n1.config.hooks[0].name = "1" Changes are highlighted precisely in logs: 11:26:49.257 INF daemon: hook: 1: +events[2]="NodeLabelsCommited" 11:26:49.257 INF daemon: hook: 1: stop listening for events [HeartbeatAlive HeartbeatStale NodeLabelsCommited] 11:26:50.054 INF daemon: hook: 1: listening for events [HeartbeatAlive HeartbeatStale] --- core/commoncmd/node_events.go | 5 +- core/node/config.go | 63 ++- core/output/renderer.go | 5 +- daemon/api/api.yaml | 22 + daemon/api/codegen_server_gen.go | 381 +++++++++--------- daemon/api/codegen_type_gen.go | 8 + daemon/api/unstructured.go | 13 + daemon/daemonapi/get_daemon_events.go | 6 +- daemon/daemonapi/get_node.go | 8 + daemon/hook/main.go | 76 ++-- daemon/msgbus/node_config.go | 2 +- daemon/nmon/config.go | 89 ++++ daemon/nmon/main.go | 2 +- daemon/nmon/main_cmd.go | 54 --- .../output/flatten.go => util/flatten/main.go | 2 +- 15 files changed, 433 insertions(+), 303 deletions(-) create mode 100644 daemon/nmon/config.go rename core/output/flatten.go => util/flatten/main.go (99%) diff --git a/core/commoncmd/node_events.go b/core/commoncmd/node_events.go index a665424c9..fb19ea9a6 100644 --- a/core/commoncmd/node_events.go +++ b/core/commoncmd/node_events.go @@ -17,6 +17,7 @@ import ( "github.com/opensvc/om3/v3/core/output" "github.com/opensvc/om3/v3/core/rawconfig" "github.com/opensvc/om3/v3/daemon/msgbus" + "github.com/opensvc/om3/v3/util/flatten" "github.com/opensvc/om3/v3/util/pubsub" ) @@ -36,7 +37,7 @@ type ( NodeSelector string errC chan error evC chan *event.Event - diff *output.Delta + diff *flatten.Delta } templateHelper struct { @@ -156,7 +157,7 @@ func (t *CmdNodeEvents) DoNodes() error { nodenames []string ) if t.Output == "diff" { - t.diff = output.NewDiff() + t.diff = flatten.NewDiff() } t.evC = make(chan *event.Event) diff --git a/core/node/config.go b/core/node/config.go index 18f9c0c3f..62d7c2cb6 100644 --- a/core/node/config.go +++ b/core/node/config.go @@ -1,16 +1,21 @@ package node import ( + "encoding/json" "maps" + "slices" "time" "github.com/opensvc/om3/v3/core/schedule" + "github.com/opensvc/om3/v3/util/flatten" "github.com/opensvc/om3/v3/util/label" + "github.com/opensvc/om3/v3/util/xmap" ) type ( Config struct { Env string `json:"env"` + Hooks Hooks `json:"hooks"` Labels label.M `json:"labels"` MaintenanceGracePeriod time.Duration `json:"maintenance_grace_period"` MaxParallel int `json:"max_parallel"` @@ -24,17 +29,26 @@ type ( SSHKey string `json:"sshkey"` PRKey string `json:"prkey"` } + + Hooks []Hook + + Hook struct { + Name string `json:"name"` + Events []string `json:"events"` + Command []string `json:"command"` + } ) func (cfg *Config) DeepCopy() *Config { newCfg := *cfg newCfg.Schedules = append([]schedule.Config{}, cfg.Schedules...) newCfg.Labels = cfg.Labels.DeepCopy() + newCfg.Hooks = cfg.Hooks.DeepCopy() return &newCfg } -func (c Config) Equals(other Config) bool { +func (c Config) Equal(other Config) bool { if c.Env != other.Env || c.MaintenanceGracePeriod != other.MaintenanceGracePeriod || c.MaxParallel != other.MaxParallel || @@ -63,5 +77,52 @@ func (c Config) Equals(other Config) bool { } } + // Compare Hook slice + if len(c.Hooks) != len(other.Hooks) { + return false + } + for i := range c.Hooks { + if !c.Hooks[i].Equal(&other.Hooks[i]) { + return false + } + } return true } + +func (t *Hook) Equal(o *Hook) bool { + if t.Name != o.Name { + return false + } else if !slices.Equal(t.Events, o.Events) { + return false + } else if !slices.Equal(t.Command, o.Command) { + return false + } + return true +} + +func (t Hooks) DeepCopy() Hooks { + l := make(Hooks, len(t)) + for i, hook := range t { + l[i] = *hook.DeepCopy() + } + return l +} + +func (t *Hook) DeepCopy() *Hook { + n := *t + n.Events = append([]string{}, t.Events...) + n.Command = append([]string{}, t.Command...) + return &n +} + +func (t *Hook) Diff(other Hook) string { + flattenable := func(hook Hook) map[string]any { + var m map[string]any + b, _ := json.Marshal(hook) + json.Unmarshal(b, &m) + return m + } + m1 := flatten.Flatten(flattenable(*t)) + m2 := flatten.Flatten(flattenable(other)) + return xmap.Diff(m1, m2) +} diff --git a/core/output/renderer.go b/core/output/renderer.go index 47b112b65..942714e52 100644 --- a/core/output/renderer.go +++ b/core/output/renderer.go @@ -15,6 +15,7 @@ import ( "k8s.io/client-go/util/jsonpath" "sigs.k8s.io/yaml" + "github.com/opensvc/om3/v3/util/flatten" "github.com/opensvc/om3/v3/util/render" "github.com/opensvc/om3/v3/util/render/palette" "github.com/opensvc/om3/v3/util/unstructured" @@ -104,9 +105,9 @@ func (t Renderer) Sprint() (string, error) { panic(err) } if color.NoColor { - return sep + SprintFlat(b), nil + return sep + flatten.SprintFlat(b), nil } else { - return sep + SprintFlatColor(b, t.Colorize), nil + return sep + flatten.SprintFlatColor(b, t.Colorize), nil } case JSON: b, err := json.MarshalIndent(t.Data, "", indent) diff --git a/daemon/api/api.yaml b/daemon/api/api.yaml index 7be9fec63..4b88430da 100644 --- a/daemon/api/api.yaml +++ b/daemon/api/api.yaml @@ -6077,6 +6077,7 @@ components: type: object required: - env + - hooks - labels - maintenance_grace_period - max_parallel @@ -6090,6 +6091,10 @@ components: properties: env: type: string + hooks: + type: array + items: + $ref: '#/components/schemas/NodeConfigHook' labels: additionalProperties: type: string @@ -6114,6 +6119,23 @@ components: type: string x-go-name: PRKey + NodeConfigHook: + type: object + required: + - command + - events + - name + properties: + command: + type: array + items: + type: string + events: + type: array + items: + type: string + name: + type: string NodeInfo: type: object required: diff --git a/daemon/api/codegen_server_gen.go b/daemon/api/codegen_server_gen.go index a65b49cc4..d71044d91 100644 --- a/daemon/api/codegen_server_gen.go +++ b/daemon/api/codegen_server_gen.go @@ -6315,196 +6315,197 @@ var swaggerSpec = []string{ "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", - "As+S1sebm5ub/xMAAP//7deHJY59AgA=", + "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", + "AgA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/api/codegen_type_gen.go b/daemon/api/codegen_type_gen.go index e4d0eae26..a02ce8747 100644 --- a/daemon/api/codegen_type_gen.go +++ b/daemon/api/codegen_type_gen.go @@ -848,6 +848,7 @@ type NodeActionAccepted struct { // NodeConfig defines model for NodeConfig. type NodeConfig struct { Env string `json:"env"` + Hooks []NodeConfigHook `json:"hooks"` Labels map[string]string `json:"labels"` MaintenanceGracePeriod time.Duration `json:"maintenance_grace_period"` MaxParallel int `json:"max_parallel"` @@ -860,6 +861,13 @@ type NodeConfig struct { SSHKey string `json:"sshkey"` } +// NodeConfigHook defines model for NodeConfigHook. +type NodeConfigHook struct { + Command []string `json:"command"` + Events []string `json:"events"` + Name string `json:"name"` +} + // NodeInfo defines model for NodeInfo. type NodeInfo struct { // Labels labels is the list of node labels. diff --git a/daemon/api/unstructured.go b/daemon/api/unstructured.go index 90039f2cb..f441994c3 100644 --- a/daemon/api/unstructured.go +++ b/daemon/api/unstructured.go @@ -115,9 +115,22 @@ func (t Node) Unstructured() map[string]any { } } +func (t *NodeConfigHook) Unstructured() map[string]any { + return map[string]any{ + "command": t.Command, + "events": t.Events, + "name": t.Name, + } +} + func (t *NodeConfig) Unstructured() map[string]any { + hooks := make([]any, 0) + for _, hook := range t.Hooks { + hooks = append(hooks, hook.Unstructured()) + } return map[string]any{ "env": t.Env, + "hooks": hooks, "labels": t.Labels, "maintenance_grace_period": t.MaintenanceGracePeriod, "min_avail_mem_pct": t.MinAvailMemPct, diff --git a/daemon/daemonapi/get_daemon_events.go b/daemon/daemonapi/get_daemon_events.go index e5ac3ec9b..cc45c8425 100644 --- a/daemon/daemonapi/get_daemon_events.go +++ b/daemon/daemonapi/get_daemon_events.go @@ -19,11 +19,11 @@ import ( "github.com/opensvc/om3/v3/core/naming" "github.com/opensvc/om3/v3/core/object" "github.com/opensvc/om3/v3/core/objectselector" - "github.com/opensvc/om3/v3/core/output" "github.com/opensvc/om3/v3/daemon/api" "github.com/opensvc/om3/v3/daemon/msgbus" "github.com/opensvc/om3/v3/daemon/rbac" "github.com/opensvc/om3/v3/util/converters" + "github.com/opensvc/om3/v3/util/flatten" "github.com/opensvc/om3/v3/util/funcopt" "github.com/opensvc/om3/v3/util/pubsub" ) @@ -609,7 +609,7 @@ func (f Filter) IsZero() bool { } func (df DataFilters) match(i any) bool { - flatten := output.Flatten(i) + keys := flatten.Flatten(i) intLessOrEqual := func(str1, str2 string) (bool, error) { num1, err1 := strconv.Atoi(str1) @@ -684,7 +684,7 @@ func (df DataFilters) match(i any) bool { } matchDataFilter := func(m DataFilter) bool { - s, ok := flatten[m.Key] + s, ok := keys[m.Key] if !ok { return false } diff --git a/daemon/daemonapi/get_node.go b/daemon/daemonapi/get_node.go index 4ba4617ac..c52c94f93 100644 --- a/daemon/daemonapi/get_node.go +++ b/daemon/daemonapi/get_node.go @@ -42,6 +42,7 @@ func (a *DaemonAPI) GetNodes(ctx echo.Context, params api.GetNodesParams) error if config.Value != nil { d.Data.Config = &api.NodeConfig{ Env: config.Value.Env, + Hooks: make([]api.NodeConfigHook, len(config.Value.Hooks)), Labels: make(map[string]string), MaintenanceGracePeriod: config.Value.MaintenanceGracePeriod, MaxParallel: config.Value.MaxParallel, @@ -53,6 +54,13 @@ func (a *DaemonAPI) GetNodes(ctx echo.Context, params api.GetNodesParams) error RejoinGracePeriod: config.Value.RejoinGracePeriod, SplitAction: config.Value.SplitAction, } + for i, hook := range config.Value.Hooks { + d.Data.Config.Hooks[i] = api.NodeConfigHook{ + Name: hook.Name, + Events: hook.Events, + Command: hook.Command, + } + } for k, v := range config.Value.Labels { d.Data.Config.Labels[k] = v } diff --git a/daemon/hook/main.go b/daemon/hook/main.go index c2304bd4f..41503ca20 100644 --- a/daemon/hook/main.go +++ b/daemon/hook/main.go @@ -6,19 +6,16 @@ import ( "fmt" "os/exec" "slices" - "strings" "sync" "sync/atomic" "syscall" "time" "github.com/opensvc/om3/v3/core/event" - "github.com/opensvc/om3/v3/core/object" + "github.com/opensvc/om3/v3/core/node" "github.com/opensvc/om3/v3/core/xconfig" "github.com/opensvc/om3/v3/daemon/msgbus" - "github.com/opensvc/om3/v3/util/command" "github.com/opensvc/om3/v3/util/hostname" - "github.com/opensvc/om3/v3/util/key" "github.com/opensvc/om3/v3/util/plog" "github.com/opensvc/om3/v3/util/pubsub" "github.com/opensvc/om3/v3/util/xmap" @@ -40,17 +37,15 @@ type ( sub *pubsub.Subscription subQS pubsub.QueueSizer - hooks hooks + hooks map[string]hook wg sync.WaitGroup } hook struct { - sig string + node.Hook cancel func() } - - hooks map[string]hook ) var ( @@ -79,37 +74,35 @@ func NewManager(drainDuration time.Duration, subQS pubsub.QueueSizer) *Manager { labelLocalhost: pubsub.Label{"node", localhost}, subQS: subQS, - hooks: make(hooks), + hooks: make(map[string]hook), } } func (t *Manager) Start(parent context.Context) error { t.log.Infof("starting") t.ctx, t.cancel = context.WithCancel(parent) - t.update() + initialNodeConfig := node.ConfigData.GetByNode(t.localhost) + if initialNodeConfig == nil { + return fmt.Errorf("node config not found for localhost: %s", t.localhost) + } + t.update(initialNodeConfig.Hooks) t.startSubscriptions() t.startUpdateLoop() t.log.Infof("started") return nil } -func (t *Manager) loadConfig() error { - n, err := object.NewNode(object.WithVolatile(false)) - if err != nil { - return err - } - t.config = n.MergedConfig() - return nil -} - func (t *Manager) startUpdateLoop() { go func() { for { select { case <-t.ctx.Done(): return - case <-t.sub.C: - t.update() + case i := <-t.sub.C: + switch ev := i.(type) { + case *msgbus.NodeConfigUpdated: + t.update(ev.Value.Hooks) + } } } }() @@ -130,28 +123,22 @@ func (t *Manager) Stop() error { return nil } -func (t *Manager) update() { - if err := t.loadConfig(); err != nil { - t.log.Warnf("%s", err) - return - } +func (t *Manager) update(hooks []node.Hook) { currentHookNames := xmap.Keys(t.hooks) var hooksToStop, scannedHooks []string - hooksToStart := make(map[string]string) - for _, name := range t.config.SectionStrings() { - if !strings.HasPrefix(name, "hook#") { - continue - } + hooksToStart := make(map[string]node.Hook) + for _, h := range hooks { + name := h.Name scannedHooks = append(scannedHooks, name) - currentHook, ok := t.hooks[name] + current, ok := t.hooks[name] if !ok { - hooksToStart[name] = "" + hooksToStart[name] = h continue } - sig := t.config.SectionSig(name) - if sig != currentHook.sig { + if diff := current.Hook.Diff(h); diff != "" { + t.log.Infof("%s: %s", name, diff) hooksToStop = append(hooksToStop, name) - hooksToStart[name] = sig + hooksToStart[name] = h } } @@ -166,23 +153,16 @@ func (t *Manager) update() { } delete(t.hooks, name) } - for name, sig := range hooksToStart { - kinds := t.config.GetStrings(key.New(name, "events")) + for name, hookToStart := range hooksToStart { + kinds := hookToStart.Events h := hook{ - sig: sig, - } - t.hooks[name] = h - s := t.config.Get(key.New(name, "command")) - args, err := command.CmdArgsFromString(s) - if err != nil { - t.log.Warnf("%s: failed to split command: %s", name, err) - continue + Hook: hookToStart, } - if len(args) < 1 { + if len(hookToStart.Command) < 1 { t.log.Warnf("%s: empty command", name) continue } - h.cancel = t.startHook(name, kinds, args) + h.cancel = t.startHook(name, kinds, hookToStart.Command) t.hooks[name] = h } } diff --git a/daemon/msgbus/node_config.go b/daemon/msgbus/node_config.go index e91958288..454185cc3 100644 --- a/daemon/msgbus/node_config.go +++ b/daemon/msgbus/node_config.go @@ -6,7 +6,7 @@ import "github.com/opensvc/om3/v3/util/pubsub" func (data *ClusterData) onNodeConfigUpdated(m *NodeConfigUpdated) { newConfig := m.Value v := data.Cluster.Node[m.Node] - if v.Config.Equals(newConfig) { + if v.Config.Equal(newConfig) { return } v.Config = m.Value diff --git a/daemon/nmon/config.go b/daemon/nmon/config.go new file mode 100644 index 000000000..f2d96009a --- /dev/null +++ b/daemon/nmon/config.go @@ -0,0 +1,89 @@ +package nmon + +import ( + "runtime" + "strings" + + "github.com/opensvc/om3/v3/core/node" + "github.com/opensvc/om3/v3/core/object" + "github.com/opensvc/om3/v3/util/key" +) + +func (t *Manager) getNodeConfig() node.Config { + var ( + keyMaintenanceGracePeriod = key.New("node", "maintenance_grace_period") + keyMaxParallel = key.New("node", "max_parallel") + keyMaxKeySize = key.New("node", "max_key_size") + keyReadyPeriod = key.New("node", "ready_period") + keyRejoinGracePeriod = key.New("node", "rejoin_grace_period") + keyEnv = key.New("node", "env") + keySplitAction = key.New("node", "split_action") + keySSHKey = key.New("node", "sshkey") + keyPRKey = key.New("node", "prkey") + keyMinAvailMemPct = key.New("node", "min_avail_mem_pct") + keyMinAvailSwapPct = key.New("node", "min_avail_swap_pct") + ) + cfg := node.Config{} + cfg.Labels = t.config.SectionMap("labels") + if d := t.config.GetDuration(keyMaintenanceGracePeriod); d != nil { + cfg.MaintenanceGracePeriod = *d + } + if d := t.config.GetDuration(keyReadyPeriod); d != nil { + cfg.ReadyPeriod = *d + } + if d := t.config.GetDuration(keyRejoinGracePeriod); d != nil { + cfg.RejoinGracePeriod = *d + } + if d := t.config.GetSize(keyMaxKeySize); d != nil { + cfg.MaxKeySize = *d + } + cfg.MinAvailMemPct = t.config.GetInt(keyMinAvailMemPct) + cfg.MinAvailSwapPct = t.config.GetInt(keyMinAvailSwapPct) + cfg.MaxParallel = t.config.GetInt(keyMaxParallel) + cfg.Env = t.config.GetString(keyEnv) + cfg.SplitAction = t.config.GetString(keySplitAction) + cfg.SSHKey = t.config.GetString(keySSHKey) + cfg.PRKey = t.config.GetString(keyPRKey) + + if cfg.MaxParallel == 0 { + cfg.MaxParallel = runtime.NumCPU() + } + if cfg.MaxParallel < MinMaxParallel { + cfg.MaxParallel = MinMaxParallel + } + + for _, s := range t.config.SectionStrings() { + if !strings.HasPrefix(s, "hook#") { + continue + } + t.log.Tracef("analyse config: %s", s) + hook := node.Hook{Name: s[5:]} + if hook.Name == "" { + t.log.Debugf("skip empty hook name for %s", s) + continue + } + hook.Events = t.config.GetStrings(key.New(s, "events")) + if len(hook.Events) == 0 { + t.log.Debugf("skip empty hook events for %s", s) + continue + } + hook.Command = t.config.GetStrings(key.New(s, "command")) + if len(hook.Command) == 0 { + t.log.Debugf("skip empty hook command for %s", s) + continue + } + cfg.Hooks = append(cfg.Hooks, hook) + t.log.Tracef("hook %s: %#v", hook.Name, hook) + } + + node, err := object.NewNode(object.WithVolatile(true)) + if err != nil { + t.log.Warnf("load node config: %s", err) + } else { + for _, e := range node.Schedules() { + cfg.Schedules = append(cfg.Schedules, e.Config) + } + } + + return cfg +} diff --git a/daemon/nmon/main.go b/daemon/nmon/main.go index b1afd972f..2ac9d6eed 100644 --- a/daemon/nmon/main.go +++ b/daemon/nmon/main.go @@ -811,7 +811,7 @@ func (t *Manager) loadConfigAndPublish() error { if labelsChanged || pathsChanged { t.saveNodesInfo() } - if !prevNodeConfig.Equals(t.nodeConfig) { + if !prevNodeConfig.Equal(t.nodeConfig) { node.ConfigData.Set(t.localhost, t.nodeConfig.DeepCopy()) t.publisher.Pub(&msgbus.NodeConfigUpdated{Node: t.localhost, Value: t.nodeConfig}, t.labelLocalhost) } diff --git a/daemon/nmon/main_cmd.go b/daemon/nmon/main_cmd.go index 2e3072ae8..a31545a7f 100644 --- a/daemon/nmon/main_cmd.go +++ b/daemon/nmon/main_cmd.go @@ -4,19 +4,16 @@ import ( "errors" "fmt" "os" - "runtime" "slices" "strings" "time" "github.com/opensvc/om3/v3/core/clusternode" "github.com/opensvc/om3/v3/core/node" - "github.com/opensvc/om3/v3/core/object" "github.com/opensvc/om3/v3/core/rawconfig" "github.com/opensvc/om3/v3/daemon/msgbus" "github.com/opensvc/om3/v3/util/errcontext" "github.com/opensvc/om3/v3/util/file" - "github.com/opensvc/om3/v3/util/key" "github.com/opensvc/om3/v3/util/toc" ) @@ -67,57 +64,6 @@ func (t *Manager) onConfigFileUpdated(_ *msgbus.ConfigFileUpdated) { t.checkRejoinTicker() } -func (t *Manager) getNodeConfig() node.Config { - var ( - keyMaintenanceGracePeriod = key.New("node", "maintenance_grace_period") - keyMaxParallel = key.New("node", "max_parallel") - keyMaxKeySize = key.New("node", "max_key_size") - keyReadyPeriod = key.New("node", "ready_period") - keyRejoinGracePeriod = key.New("node", "rejoin_grace_period") - keyEnv = key.New("node", "env") - keySplitAction = key.New("node", "split_action") - keySSHKey = key.New("node", "sshkey") - keyPRKey = key.New("node", "prkey") - keyMinAvailMemPct = key.New("node", "min_avail_mem_pct") - keyMinAvailSwapPct = key.New("node", "min_avail_swap_pct") - ) - cfg := node.Config{} - cfg.Labels = t.config.SectionMap("labels") - if d := t.config.GetDuration(keyMaintenanceGracePeriod); d != nil { - cfg.MaintenanceGracePeriod = *d - } - if d := t.config.GetDuration(keyReadyPeriod); d != nil { - cfg.ReadyPeriod = *d - } - if d := t.config.GetDuration(keyRejoinGracePeriod); d != nil { - cfg.RejoinGracePeriod = *d - } - if d := t.config.GetSize(keyMaxKeySize); d != nil { - cfg.MaxKeySize = *d - } - cfg.MinAvailMemPct = t.config.GetInt(keyMinAvailMemPct) - cfg.MinAvailSwapPct = t.config.GetInt(keyMinAvailSwapPct) - cfg.MaxParallel = t.config.GetInt(keyMaxParallel) - cfg.Env = t.config.GetString(keyEnv) - cfg.SplitAction = t.config.GetString(keySplitAction) - cfg.SSHKey = t.config.GetString(keySSHKey) - cfg.PRKey = t.config.GetString(keyPRKey) - - if cfg.MaxParallel == 0 { - cfg.MaxParallel = runtime.NumCPU() - } - if cfg.MaxParallel < MinMaxParallel { - cfg.MaxParallel = MinMaxParallel - } - - node, _ := object.NewNode(object.WithVolatile(true)) - for _, e := range node.Schedules() { - cfg.Schedules = append(cfg.Schedules, e.Config) - } - - return cfg -} - func (t *Manager) checkRejoinTicker() { if t.state.State != node.MonitorStateRejoin { return diff --git a/core/output/flatten.go b/util/flatten/main.go similarity index 99% rename from core/output/flatten.go rename to util/flatten/main.go index a9a31346a..c4ab7cc44 100644 --- a/core/output/flatten.go +++ b/util/flatten/main.go @@ -1,4 +1,4 @@ -package output +package flatten import ( "bytes" From 085892a5bf177e1c610846c397e6eb8fd36c06c6 Mon Sep 17 00:00:00 2001 From: Christophe Varoqui Date: Fri, 20 Feb 2026 15:09:01 +0100 Subject: [PATCH 5/6] Fix the scheduler max_parallel abort check The scheduler must avoid executing the CRM command when it knows it will produce a max_parallel overflow logged as an error. --- core/omcmd/node_schedule_list.go | 1 - core/omcmd/object_schedule_list.go | 1 - core/schedule/main.go | 6 +- daemon/api/api.yaml | 6 - daemon/api/codegen_server_gen.go | 273 +++++++++++----------- daemon/api/codegen_type_gen.go | 2 - daemon/api/unstructured.go | 1 - daemon/daemonapi/get_instance_schedule.go | 1 - daemon/daemonapi/get_node_schedule.go | 1 - 9 files changed, 139 insertions(+), 153 deletions(-) diff --git a/core/omcmd/node_schedule_list.go b/core/omcmd/node_schedule_list.go index 129400cc0..06f2d3fe7 100644 --- a/core/omcmd/node_schedule_list.go +++ b/core/omcmd/node_schedule_list.go @@ -67,7 +67,6 @@ func (t *CmdNodeScheduleList) extractLocal() (api.ScheduleItems, error) { RequireCollector: e.RequireCollector, RequireProvisioned: e.RequireProvisioned, Schedule: e.Schedule, - StatefileKey: e.StatefileKey, }, } items = append(items, item) diff --git a/core/omcmd/object_schedule_list.go b/core/omcmd/object_schedule_list.go index bc0037bd5..e576b4761 100644 --- a/core/omcmd/object_schedule_list.go +++ b/core/omcmd/object_schedule_list.go @@ -79,7 +79,6 @@ func (t *CmdObjectScheduleList) extractLocal(selector string) (api.ScheduleList, RequireCollector: e.RequireCollector, RequireProvisioned: e.RequireProvisioned, Schedule: e.Schedule, - StatefileKey: e.StatefileKey, }, } data.Items = append(data.Items, item) diff --git a/core/schedule/main.go b/core/schedule/main.go index ef02ccce1..fb2aee089 100644 --- a/core/schedule/main.go +++ b/core/schedule/main.go @@ -22,10 +22,11 @@ type ( Require string `json:"require,omitempty"` RequireCollector bool `json:"require_collector"` RequireProvisioned bool `json:"require_provisioned"` + RunDir string `json:"-"` // StatefileKey is used in the last run filename and last run success formatters. // Defaults to Action if empty. - StatefileKey string `json:"statefile_key,omitempty"` + StatefileKey string `json:"-"` } Entry struct { @@ -34,7 +35,6 @@ type ( NextRunAt time.Time `json:"next_run_at"` Node string `json:"node"` Path naming.Path `json:"path"` - RunDir string `json:"run_dir"` } ) @@ -114,6 +114,7 @@ func (t Table) DeepCopy() *Table { Require: x.Require, RequireCollector: x.RequireCollector, RequireProvisioned: x.RequireProvisioned, + RunDir: x.RunDir, Schedule: x.Schedule, StatefileKey: x.StatefileKey, }, @@ -121,7 +122,6 @@ func (t Table) DeepCopy() *Table { NextRunAt: x.NextRunAt, Node: x.Node, Path: x.Path, - RunDir: x.RunDir, }) } return &r diff --git a/daemon/api/api.yaml b/daemon/api/api.yaml index 4b88430da..27862b96f 100644 --- a/daemon/api/api.yaml +++ b/daemon/api/api.yaml @@ -7460,7 +7460,6 @@ components: - key - max_parallel - last_run_at - - statefile_key - next_run_at - require - require_collector @@ -7475,8 +7474,6 @@ components: last_run_at: type: string format: date-time - statefile_key: - type: string max_parallel: type: integer next_run_at: @@ -7495,7 +7492,6 @@ components: - action - schedule - key - - statefile_key - max_parallel - require - require_collector @@ -7507,8 +7503,6 @@ components: type: string key: type: string - statefile_key: - type: string max_parallel: type: integer require: diff --git a/daemon/api/codegen_server_gen.go b/daemon/api/codegen_server_gen.go index d71044d91..bf8fb12f0 100644 --- a/daemon/api/codegen_server_gen.go +++ b/daemon/api/codegen_server_gen.go @@ -6369,143 +6369,142 @@ var swaggerSpec = []string{ "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", - "AgA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/api/codegen_type_gen.go b/daemon/api/codegen_type_gen.go index a02ce8747..fa3bc05b1 100644 --- a/daemon/api/codegen_type_gen.go +++ b/daemon/api/codegen_type_gen.go @@ -1564,7 +1564,6 @@ type Schedule struct { RequireCollector bool `json:"require_collector"` RequireProvisioned bool `json:"require_provisioned"` Schedule string `json:"schedule"` - StatefileKey string `json:"statefile_key"` } // ScheduleConfig defines model for ScheduleConfig. @@ -1576,7 +1575,6 @@ type ScheduleConfig struct { RequireCollector bool `json:"require_collector"` RequireProvisioned bool `json:"require_provisioned"` Schedule string `json:"schedule"` - StatefileKey string `json:"statefile_key"` } // ScheduleItem defines model for ScheduleItem. diff --git a/daemon/api/unstructured.go b/daemon/api/unstructured.go index f441994c3..2da4effbd 100644 --- a/daemon/api/unstructured.go +++ b/daemon/api/unstructured.go @@ -85,7 +85,6 @@ func (t Schedule) Unstructured() map[string]any { "require": t.Require, "require_collector": t.RequireCollector, "require_Provisioned": t.RequireProvisioned, - "statefile_key": t.StatefileKey, } } diff --git a/daemon/daemonapi/get_instance_schedule.go b/daemon/daemonapi/get_instance_schedule.go index ac8d88adc..4c6bc9ead 100644 --- a/daemon/daemonapi/get_instance_schedule.go +++ b/daemon/daemonapi/get_instance_schedule.go @@ -57,7 +57,6 @@ func (a *DaemonAPI) getLocalInstanceSchedule(ctx echo.Context, namespace string, RequireCollector: e.RequireCollector, RequireProvisioned: e.RequireProvisioned, Schedule: e.Schedule, - StatefileKey: e.StatefileKey, }, } resp.Items = append(resp.Items, item) diff --git a/daemon/daemonapi/get_node_schedule.go b/daemon/daemonapi/get_node_schedule.go index a34c92136..9b7426530 100644 --- a/daemon/daemonapi/get_node_schedule.go +++ b/daemon/daemonapi/get_node_schedule.go @@ -47,7 +47,6 @@ func (a *DaemonAPI) getLocalSchedule(ctx echo.Context) error { RequireCollector: e.RequireCollector, RequireProvisioned: e.RequireProvisioned, Schedule: e.Schedule, - StatefileKey: e.StatefileKey, }, } resp.Items = append(resp.Items, item) From b7ecee7af4210c89d17c0fb1a6f546a5adf775c4 Mon Sep 17 00:00:00 2001 From: Christophe Varoqui Date: Fri, 20 Feb 2026 17:47:35 +0100 Subject: [PATCH 6/6] Add the collector config to node.Config Emit NodeConfigUpdated when the collector config changes. Also make the collector daemon subsystem use node.Config.Collector directly to prepare the requester, without having to allocate a object.NewNode() ... the shared function are now all in the core/collector package. --- core/collector/config.go | 44 +++ core/collector/rpc.go | 19 +- core/node/config.go | 22 +- core/object/node_collector.go | 48 +-- daemon/api/api.yaml | 20 ++ daemon/api/codegen_server_gen.go | 537 ++++++++++++++++--------------- daemon/api/codegen_type_gen.go | 33 +- daemon/collector/main.go | 29 +- daemon/collector/on_events.go | 51 ++- daemon/daemonapi/get_node.go | 8 + daemon/nmon/config.go | 1 + 11 files changed, 451 insertions(+), 361 deletions(-) create mode 100644 core/collector/config.go diff --git a/core/collector/config.go b/core/collector/config.go new file mode 100644 index 000000000..a521bda9f --- /dev/null +++ b/core/collector/config.go @@ -0,0 +1,44 @@ +package collector + +import ( + "errors" + "time" +) + +type ( + Config struct { + FeederUrl string `json:"feeder_url"` + ServerUrl string `json:"server_url"` + Timeout time.Duration `json:"timeout"` + Insecure bool `json:"insecure"` + + // Hidden fields + Password string `json:"-"` + } +) + +var ( + ErrConfig = errors.New("collector is not configured: empty configuration keyword node.dbopensvc") + ErrUnregistered = errors.New("this node is not registered. try 'om node register'") +) + +func (t *Config) Equal(o *Config) bool { + if t == nil && o != nil { + return false + } + if t != nil && o == nil { + return false + } + if t != nil && o != nil && *t != *o { + return false + } + return true +} + +func (t *Config) DeepCopy() *Config { + if t == nil { + return nil + } + n := *t + return &n +} diff --git a/core/collector/rpc.go b/core/collector/rpc.go index 72b3b66e1..28f4942ba 100644 --- a/core/collector/rpc.go +++ b/core/collector/rpc.go @@ -3,6 +3,7 @@ package collector import ( "context" "crypto/tls" + "encoding/base64" "fmt" "net/http" "net/url" @@ -14,6 +15,7 @@ import ( "github.com/ybbus/jsonrpc" "github.com/opensvc/om3/v3/util/hostname" + "github.com/opensvc/om3/v3/util/httphelper" "github.com/opensvc/om3/v3/util/plog" ) @@ -186,13 +188,24 @@ func BaseURL(s string) (*url.URL, error) { return u, nil } +func (c *Config) NewFeedRequester() (*httphelper.T, error) { + if c.FeederUrl == "" { + return nil, ErrConfig + } else if c.Password == "" { + return nil, ErrUnregistered + } + + auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(hostname.Hostname()+":"+c.Password)) + return NewRequester(c.FeederUrl, auth, c.Insecure) +} + // NewFeedClient returns a Client to call the collector feed app jsonrpc2 methods. -func NewFeedClient(endpoint, secret string) (*Client, error) { - u, err := FeedURL(endpoint) +func (c *Config) NewFeedClient() (*Client, error) { + u, err := FeedURL(c.FeederUrl) if err != nil { return nil, err } - return newClient(u, secret) + return newClient(u, c.Password) } // NewComplianceClient returns a Client to call the collector init app jsonrpc2 methods. diff --git a/core/node/config.go b/core/node/config.go index 62d7c2cb6..387ad20c0 100644 --- a/core/node/config.go +++ b/core/node/config.go @@ -6,6 +6,7 @@ import ( "slices" "time" + "github.com/opensvc/om3/v3/core/collector" "github.com/opensvc/om3/v3/core/schedule" "github.com/opensvc/om3/v3/util/flatten" "github.com/opensvc/om3/v3/util/label" @@ -14,6 +15,7 @@ import ( type ( Config struct { + Collector *collector.Config `json:"collector,omitempty"` Env string `json:"env"` Hooks Hooks `json:"hooks"` Labels label.M `json:"labels"` @@ -44,6 +46,7 @@ func (cfg *Config) DeepCopy() *Config { newCfg.Schedules = append([]schedule.Config{}, cfg.Schedules...) newCfg.Labels = cfg.Labels.DeepCopy() newCfg.Hooks = cfg.Hooks.DeepCopy() + newCfg.Collector = cfg.Collector.DeepCopy() return &newCfg } @@ -63,6 +66,9 @@ func (c Config) Equal(other Config) bool { return false } + if !c.Collector.Equal(other.Collector) { + return false + } if !maps.Equal(c.Labels, other.Labels) { return false } @@ -89,6 +95,14 @@ func (c Config) Equal(other Config) bool { return true } +func (t Hooks) DeepCopy() Hooks { + l := make(Hooks, len(t)) + for i, hook := range t { + l[i] = *hook.DeepCopy() + } + return l +} + func (t *Hook) Equal(o *Hook) bool { if t.Name != o.Name { return false @@ -100,14 +114,6 @@ func (t *Hook) Equal(o *Hook) bool { return true } -func (t Hooks) DeepCopy() Hooks { - l := make(Hooks, len(t)) - for i, hook := range t { - l[i] = *hook.DeepCopy() - } - return l -} - func (t *Hook) DeepCopy() *Hook { n := *t n.Events = append([]string{}, t.Events...) diff --git a/core/object/node_collector.go b/core/object/node_collector.go index a9bf55d9d..26df060e8 100644 --- a/core/object/node_collector.go +++ b/core/object/node_collector.go @@ -4,7 +4,6 @@ import ( "crypto/tls" "encoding/base64" "encoding/json" - "errors" "fmt" "net/http" "net/url" @@ -28,25 +27,12 @@ type ( uuid string } - CollectorConfig struct { - FeederUrl string `json:"feeder_url"` - ServerUrl string `json:"server_url"` - Timeout time.Duration `json:"timeout"` - Insecure bool `json:"insecure"` - - // private field - password string - } - CollectorProblem struct { text string `json:"text"` } ) var ( - ErrNodeCollectorConfig = errors.New("collector is not configured: empty configuration keyword node.dbopensvc") - ErrNodeCollectorUnregistered = errors.New("this node is not registered. try 'om node register'") - defaultPostCollectorTimeout = 1 * time.Second ) @@ -104,23 +90,23 @@ func (t *CollectorConfigRaw) ServerUrl() string { } } -func (t *CollectorConfigRaw) AsConfig() *CollectorConfig { +func (t *CollectorConfigRaw) AsConfig() *collector.Config { var timeout time.Duration if t.timeout != nil { timeout = *t.timeout } - return &CollectorConfig{ + return &collector.Config{ FeederUrl: t.FeederUrl(), ServerUrl: t.ServerUrl(), Timeout: timeout, Insecure: t.insecure, - password: t.uuid, + Password: t.uuid, } } func (t *Node) CollectorFeedClient() (*collector.Client, error) { - collectorCfg := t.CollectorRawConfig().AsConfig() - return collector.NewFeedClient(collectorCfg.FeederUrl, collectorCfg.password) + cfg := t.CollectorRawConfig().AsConfig() + return cfg.NewFeedClient() } func (t Node) CollectorInitClient() (*collector.Client, error) { @@ -171,11 +157,11 @@ func (t *Node) CollectorClient() (*httphelper.T, error) { pass := t.MergedConfig().GetString(key.Parse("node.uuid")) if dbopensvc == "" || dbopensvc == "none" { - return nil, ErrNodeCollectorConfig + return nil, collector.ErrConfig } if dbopensvc != "" && pass == "" { - return nil, ErrNodeCollectorUnregistered + return nil, collector.ErrUnregistered } auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(hostname.Hostname()+":"+pass)) @@ -187,12 +173,12 @@ func (t *Node) CollectorFeeder() (*httphelper.T, error) { cfg := t.CollectorRawConfig().AsConfig() if cfg.FeederUrl == "" { - return nil, ErrNodeCollectorConfig - } else if cfg.password == "" { - return nil, ErrNodeCollectorUnregistered + return nil, collector.ErrConfig + } else if cfg.Password == "" { + return nil, collector.ErrUnregistered } - auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(hostname.Hostname()+":"+cfg.password)) + auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(hostname.Hostname()+":"+cfg.Password)) return collector.NewRequester(cfg.FeederUrl, auth, cfg.Insecure) } @@ -201,12 +187,12 @@ func (t *Node) CollectorServer() (*httphelper.T, error) { cfg := t.CollectorRawConfig().AsConfig() if cfg.ServerUrl == "" { - return nil, ErrNodeCollectorConfig - } else if cfg.password == "" { - return nil, ErrNodeCollectorUnregistered + return nil, collector.ErrConfig + } else if cfg.Password == "" { + return nil, collector.ErrUnregistered } - auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(hostname.Hostname()+":"+cfg.password)) + auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(hostname.Hostname()+":"+cfg.Password)) return collector.NewRequester(cfg.ServerUrl, auth, cfg.Insecure) } @@ -215,7 +201,7 @@ func (t *Node) CollectorServerWithAuth(auth string) (*httphelper.T, error) { cfg := t.CollectorRawConfig().AsConfig() if cfg.ServerUrl == "" { - return nil, ErrNodeCollectorConfig + return nil, collector.ErrConfig } return collector.NewRequester(cfg.ServerUrl, auth, cfg.Insecure) @@ -226,7 +212,7 @@ func (t *Node) CollectorServerWithoutAuth() (*httphelper.T, error) { cfg := t.CollectorRawConfig().AsConfig() if cfg.ServerUrl == "" { - return nil, ErrNodeCollectorConfig + return nil, collector.ErrConfig } return collector.NewRequester(cfg.ServerUrl, "", cfg.Insecure) diff --git a/daemon/api/api.yaml b/daemon/api/api.yaml index 27862b96f..41e2b65fe 100644 --- a/daemon/api/api.yaml +++ b/daemon/api/api.yaml @@ -6089,6 +6089,8 @@ components: - sshkey - prkey properties: + collector: + $ref: '#/components/schemas/NodeConfigCollector' env: type: string hooks: @@ -6119,6 +6121,23 @@ components: type: string x-go-name: PRKey + NodeConfigCollector: + type: object + required: + - feeder_url + - insecure + - server_url + - timeout + properties: + feeder_url: + type: string + insecure: + type: boolean + server_url: + type: string + timeout: + x-go-type: time.Duration + NodeConfigHook: type: object required: @@ -6136,6 +6155,7 @@ components: type: string name: type: string + NodeInfo: type: object required: diff --git a/daemon/api/codegen_server_gen.go b/daemon/api/codegen_server_gen.go index bf8fb12f0..e91b4fe7c 100644 --- a/daemon/api/codegen_server_gen.go +++ b/daemon/api/codegen_server_gen.go @@ -6230,281 +6230,282 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/api/codegen_type_gen.go b/daemon/api/codegen_type_gen.go index fa3bc05b1..32b9727cd 100644 --- a/daemon/api/codegen_type_gen.go +++ b/daemon/api/codegen_type_gen.go @@ -847,18 +847,27 @@ type NodeActionAccepted struct { // NodeConfig defines model for NodeConfig. type NodeConfig struct { - Env string `json:"env"` - Hooks []NodeConfigHook `json:"hooks"` - Labels map[string]string `json:"labels"` - MaintenanceGracePeriod time.Duration `json:"maintenance_grace_period"` - MaxParallel int `json:"max_parallel"` - MinAvailMemPct int `json:"min_avail_mem_pct"` - MinAvailSwapPct int `json:"min_avail_swap_pct"` - PRKey string `json:"prkey"` - ReadyPeriod time.Duration `json:"ready_period"` - RejoinGracePeriod time.Duration `json:"rejoin_grace_period"` - SplitAction string `json:"split_action"` - SSHKey string `json:"sshkey"` + Collector *NodeConfigCollector `json:"collector,omitempty"` + Env string `json:"env"` + Hooks []NodeConfigHook `json:"hooks"` + Labels map[string]string `json:"labels"` + MaintenanceGracePeriod time.Duration `json:"maintenance_grace_period"` + MaxParallel int `json:"max_parallel"` + MinAvailMemPct int `json:"min_avail_mem_pct"` + MinAvailSwapPct int `json:"min_avail_swap_pct"` + PRKey string `json:"prkey"` + ReadyPeriod time.Duration `json:"ready_period"` + RejoinGracePeriod time.Duration `json:"rejoin_grace_period"` + SplitAction string `json:"split_action"` + SSHKey string `json:"sshkey"` +} + +// NodeConfigCollector defines model for NodeConfigCollector. +type NodeConfigCollector struct { + FeederUrl string `json:"feeder_url"` + Insecure bool `json:"insecure"` + ServerUrl string `json:"server_url"` + Timeout time.Duration `json:"timeout"` } // NodeConfigHook defines model for NodeConfigHook. diff --git a/daemon/collector/main.go b/daemon/collector/main.go index 74e140d80..0a26365b9 100644 --- a/daemon/collector/main.go +++ b/daemon/collector/main.go @@ -213,10 +213,12 @@ func New(ctx context.Context, subQS pubsub.QueueSizer, opts ...funcopt.O) *T { return t } -func (t *T) setNodeFeedClient() error { - if node, err := object.NewNode(); err != nil { - return err - } else if client, err := node.CollectorFeedClient(); err != nil { +func (t *T) setNodeFeedClient(c *collector.Config) error { + if c == nil { + t.feedClient = nil + return nil + } else if client, err := c.NewFeedClient(); err != nil { + t.feedClient = nil return err } else { t.feedClient = client @@ -225,16 +227,18 @@ func (t *T) setNodeFeedClient() error { } } -func (t *T) setupRequester() error { +func (t *T) setupRequester(c *collector.Config) error { // TODO: pickup dbopensvc, auth, insecure from config update message // to create requester from core/collector.NewRequester t.status.ConfiguredAt = time.Now() - if node, err := object.NewNode(); err != nil { + if c == nil { t.client = nil - return err - } else if cli, err := node.CollectorFeeder(); err != nil { + t.disable = true + t.status.Url = "" + return nil + } else if cli, err := c.NewFeedRequester(); err != nil { t.client = nil - if errors.Is(err, object.ErrNodeCollectorConfig) { + if errors.Is(err, collector.ErrConfig) { t.disable = true t.status.Url = "" err = nil @@ -260,9 +264,12 @@ func (t *T) Start(ctx context.Context) error { t.publisher = pubsub.PubFromContext(t.ctx) t.wg.Add(1) + + initialNodeConfig := node.ConfigData.GetByNode(t.localhost) + go func(errC chan<- error) { defer t.wg.Done() - if err := t.setNodeFeedClient(); err != nil { + if err := t.setNodeFeedClient(initialNodeConfig.Collector); err != nil { t.log.Infof("the collector routine is dormant: %s", err) } else { t.log.Infof("feeding %s", t.feedClient) @@ -270,7 +277,7 @@ func (t *T) Start(ctx context.Context) error { t.feedPinger.Start(t.ctx, FeedPingerInterval) defer t.feedPinger.Stop() } - if err := t.setupRequester(); err != nil { + if err := t.setupRequester(initialNodeConfig.Collector); err != nil { t.log.Errorf("can't setup requester: %s", err) } errC <- nil diff --git a/daemon/collector/on_events.go b/daemon/collector/on_events.go index ba3cd04e3..32f49d791 100644 --- a/daemon/collector/on_events.go +++ b/daemon/collector/on_events.go @@ -8,7 +8,6 @@ import ( "github.com/opensvc/om3/v3/core/collector" "github.com/opensvc/om3/v3/core/instance" "github.com/opensvc/om3/v3/core/naming" - "github.com/opensvc/om3/v3/core/object" "github.com/opensvc/om3/v3/daemon/msgbus" ) @@ -42,32 +41,6 @@ func (t *T) onClusterConfigUpdated(c *msgbus.ClusterConfigUpdated) { } } -func (t *T) onConfigUpdated() { - t.log.Tracef("reconfigure") - if collector.Alive.Load() { - t.log.Infof("disable collector clients") - collector.Alive.Store(false) - } - err := t.setNodeFeedClient() - if t.feedPinger != nil { - t.feedPinger.Stop() - } - if err := t.setupRequester(); err != nil { - if !errors.Is(err, object.ErrNodeCollectorConfig) { - t.log.Errorf("can't setup requester: %s", err) - } - } - if err != nil { - t.log.Infof("the collector routine is dormant: %s", err) - } else { - t.log.Infof("feeding %s", t.feedClient) - t.feedPinger = t.feedClient.NewPinger() - time.Sleep(time.Microsecond * 10) - t.feedPinger.Start(t.ctx, FeedPingerInterval) - } - t.publishOnChange(t.getState()) -} - func (t *T) onInstanceConfigDeleted(c *msgbus.InstanceConfigDeleted) { if instanceConfig, ok := t.objectConfigToSend[c.Path]; ok { if instanceConfig == nil { @@ -145,7 +118,29 @@ func (t *T) onInstanceStatusUpdated(c *msgbus.InstanceStatusUpdated) { } func (t *T) onNodeConfigUpdated(c *msgbus.NodeConfigUpdated) { - t.onConfigUpdated() + t.log.Tracef("reconfigure") + if collector.Alive.Load() { + t.log.Infof("disable collector clients") + collector.Alive.Store(false) + } + err := t.setNodeFeedClient(c.Value.Collector) + if t.feedPinger != nil { + t.feedPinger.Stop() + } + if err := t.setupRequester(c.Value.Collector); err != nil { + if !errors.Is(err, collector.ErrConfig) { + t.log.Errorf("can't setup requester: %s", err) + } + } + if err != nil { + t.log.Infof("the collector routine is dormant: %s", err) + } else { + t.log.Infof("feeding %s", t.feedClient) + t.feedPinger = t.feedClient.NewPinger() + time.Sleep(time.Microsecond * 10) + t.feedPinger.Start(t.ctx, FeedPingerInterval) + } + t.publishOnChange(t.getState()) } func (t *T) onNodeMonitorDeleted(c *msgbus.NodeMonitorDeleted) { diff --git a/daemon/daemonapi/get_node.go b/daemon/daemonapi/get_node.go index c52c94f93..0d1304d08 100644 --- a/daemon/daemonapi/get_node.go +++ b/daemon/daemonapi/get_node.go @@ -54,6 +54,14 @@ func (a *DaemonAPI) GetNodes(ctx echo.Context, params api.GetNodesParams) error RejoinGracePeriod: config.Value.RejoinGracePeriod, SplitAction: config.Value.SplitAction, } + if c := config.Value.Collector; c != nil { + d.Data.Config.Collector = &api.NodeConfigCollector{ + FeederUrl: c.FeederUrl, + Insecure: c.Insecure, + ServerUrl: c.ServerUrl, + Timeout: c.Timeout, + } + } for i, hook := range config.Value.Hooks { d.Data.Config.Hooks[i] = api.NodeConfigHook{ Name: hook.Name, diff --git a/daemon/nmon/config.go b/daemon/nmon/config.go index f2d96009a..1cbd0378a 100644 --- a/daemon/nmon/config.go +++ b/daemon/nmon/config.go @@ -83,6 +83,7 @@ func (t *Manager) getNodeConfig() node.Config { for _, e := range node.Schedules() { cfg.Schedules = append(cfg.Schedules, e.Config) } + cfg.Collector = node.CollectorRawConfig().AsConfig() } return cfg