master
go 189 lines 5.24 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package jobruntime
4
5 import (
6 "fmt"
7 "maps"
8 "strings"
9
10 "github.com/netdata/netdata/go/plugins/pkg/metrix"
11 "github.com/netdata/netdata/go/plugins/pkg/netdataapi"
12 "github.com/netdata/netdata/go/plugins/plugin/framework/chartemit"
13 "github.com/netdata/netdata/go/plugins/plugin/framework/chartengine"
14 "github.com/netdata/netdata/go/plugins/plugin/framework/vnoderegistry"
15 "github.com/netdata/netdata/go/plugins/plugin/framework/vnodes"
16 )
17
18 type jobV2HostKind uint8
19
20 const (
21 jobV2HostUnset jobV2HostKind = iota
22 jobV2HostGlobal
23 jobV2HostVnode
24 )
25
26 type jobV2HostRef struct {
27 kind jobV2HostKind
28 guid string
29 }
30
31 func jobV2HostFromVnode(vnode vnodes.VirtualNode) jobV2HostRef {
32 if vnode.GUID == "" {
33 return jobV2HostRef{kind: jobV2HostGlobal}
34 }
35 return jobV2HostRef{kind: jobV2HostVnode, guid: vnode.GUID}
36 }
37
38 func (r jobV2HostRef) isSet() bool { return r.kind != jobV2HostUnset }
39 func (r jobV2HostRef) isGlobal() bool { return r.kind == jobV2HostGlobal }
40 func (r jobV2HostRef) isVnode() bool { return r.kind == jobV2HostVnode }
41
42 type jobV2EmissionDecision struct {
43 targetHost jobV2HostRef
44 needEngineReload bool
45 hostScope *chartemit.HostScope
46 defineInfo netdataapi.HostInfo
47 registryOwner vnoderegistry.Owner
48 registryRegistration vnoderegistry.Registration
49 }
50
51 type jobV2HostState struct {
52 definedHost jobV2HostRef
53 definedInfo netdataapi.HostInfo
54 engineHost jobV2HostRef
55 cleanupOwner jobV2HostRef
56 cleanupCharts map[string]chartengine.ChartMeta
57 // registryOwners tracks successfully emitted vnode owners so cleanup can
58 // release them after obsolete-chart emission.
59 registryOwners map[vnoderegistry.Owner]string
60 }
61
62 func (s *jobV2HostState) invalidateDefine() {
63 if s == nil {
64 return
65 }
66 s.definedHost = jobV2HostRef{}
67 s.definedInfo = netdataapi.HostInfo{}
68 }
69
70 func (s *jobV2HostState) prepareEmission(vnode vnodes.VirtualNode) (jobV2EmissionDecision, error) {
71 target := jobV2HostFromVnode(vnode)
72 decision := jobV2EmissionDecision{
73 targetHost: target,
74 needEngineReload: s != nil && s.engineHost.isSet() && s.engineHost != target,
75 }
76 if target.isGlobal() {
77 return decision, nil
78 }
79
80 scope := &chartemit.HostScope{GUID: target.guid}
81 decision.hostScope = scope
82 return decision, nil
83 }
84
85 func (s *jobV2HostState) prepareScopedEmission(scope metrix.HostScope) (jobV2EmissionDecision, error) {
86 target := jobV2HostRef{kind: jobV2HostVnode, guid: scope.GUID}
87 decision := jobV2EmissionDecision{
88 targetHost: target,
89 needEngineReload: s != nil && s.engineHost.isSet() && s.engineHost != target,
90 hostScope: &chartemit.HostScope{GUID: target.guid},
91 }
92 return decision, nil
93 }
94
95 func (s *jobV2HostState) commitSuccessfulEmission(plan chartengine.Plan, decision jobV2EmissionDecision) {
96 if s == nil {
97 return
98 }
99 if len(plan.Actions) == 0 {
100 if decision.needEngineReload {
101 // Quiet host switches still need to finish after the scope attempt commits.
102 s.engineHost = decision.targetHost
103 }
104 return
105 }
106 s.engineHost = decision.targetHost
107 if decision.hostScope != nil {
108 s.definedHost = decision.targetHost
109 s.definedInfo = decision.defineInfo
110 }
111 if decision.registryOwner != "" {
112 if s.registryOwners == nil {
113 s.registryOwners = make(map[vnoderegistry.Owner]string)
114 }
115 s.registryOwners[decision.registryOwner] = decision.targetHost.guid
116 }
117 if s.cleanupCharts == nil {
118 s.cleanupCharts = make(map[string]chartengine.ChartMeta)
119 }
120
121 createCharts := make(map[string]chartengine.ChartMeta)
122 dimensionOnlyCharts := make(map[string]chartengine.ChartMeta)
123 removeCharts := make(map[string]struct{})
124
125 for _, action := range plan.Actions {
126 switch v := action.(type) {
127 case chartengine.CreateChartAction:
128 createCharts[v.ChartID] = v.Meta
129 case chartengine.CreateDimensionAction:
130 if _, ok := createCharts[v.ChartID]; ok {
131 continue
132 }
133 if _, ok := dimensionOnlyCharts[v.ChartID]; !ok {
134 dimensionOnlyCharts[v.ChartID] = v.ChartMeta
135 }
136 case chartengine.RemoveChartAction:
137 removeCharts[v.ChartID] = struct{}{}
138 }
139 }
140
141 maps.Copy(s.cleanupCharts, createCharts)
142 for chartID, meta := range dimensionOnlyCharts {
143 if _, ok := s.cleanupCharts[chartID]; ok {
144 continue
145 }
146 s.cleanupCharts[chartID] = meta
147 }
148 for chartID := range removeCharts {
149 delete(s.cleanupCharts, chartID)
150 }
151
152 s.cleanupOwner = decision.targetHost
153 }
154
155 func (s *jobV2HostState) releaseRegistryOwners(registry *vnoderegistry.Registry) {
156 if s == nil || registry == nil || len(s.registryOwners) == 0 {
157 return
158 }
159 for owner, guid := range s.registryOwners {
160 registry.Release(owner, guid)
161 delete(s.registryOwners, owner)
162 }
163 }
164
165 func (s *jobV2HostState) releaseSupersededRegistryOwnersExcept(registry *vnoderegistry.Registry, current map[vnoderegistry.Owner]struct{}, ownerPrefix string) {
166 if s == nil || registry == nil || len(s.registryOwners) == 0 {
167 return
168 }
169 for owner, guid := range s.registryOwners {
170 if _, ok := current[owner]; ok || !strings.HasPrefix(string(owner), ownerPrefix) {
171 continue
172 }
173 registry.Release(owner, guid)
174 delete(s.registryOwners, owner)
175 }
176 }
177
178 func (r jobV2HostRef) String() string {
179 switch r.kind {
180 case jobV2HostUnset:
181 return "unset"
182 case jobV2HostGlobal:
183 return "global"
184 case jobV2HostVnode:
185 return fmt.Sprintf("vnode(%s)", r.guid)
186 default:
187 return "unknown"
188 }
189 }