manager.go

v0.2.0
Doc Versions Source
1
package steward
2
3
import (
4
	"context"
5
	"errors"
6
	"strings"
7
	"sync"
8
9
	"github.com/google/uuid"
10
	"github.com/soverenio/vanilla/throw"
11
12
	"go.bigb.es/auxilia/collect"
13
	"go.bigb.es/auxilia/collect/set"
14
	"go.bigb.es/auxilia/internal/logutil"
15
	"go.bigb.es/auxilia/internal/syncutil"
16
)
17
18
type ManagerStatus int
19
20
const (
21
	ManagerUnknown ManagerStatus = iota
22
	ManagerCreated
23
	ManagerInitialized
24
	ManagerInjected
25
	ManagerStarted
26
	ManagerStopped
27
	ManagerDestroyed
28
)
29
30
type children = map[uuid.UUID]*Manager
31
32
type Manager struct {
33
	componentsLock sync.Mutex
34
	status         ManagerStatus
35
36
	id        uuid.UUID
37
	parent    *Manager
38
	child     *syncutil.LockedImmutable[children]
39
	onCleanup func()
40
41
	startOrder    []*serviceAsset // stop order is reversed
42
	overrides     []*serviceAsset
43
	components    []*serviceAsset
44
	configuration []*configurationAsset
45
}
46
47
func NewManager() *Manager {
48
	manager := &Manager{
49
		status:    ManagerCreated,
50
		id:        uuid.New(),
51
		onCleanup: func() {},
52
		child:     syncutil.NewLockedImmutable(children{}),
53
	}
54
55
	manager._addComponent(context.Background(),
56
		MustServiceAsset(&Introspector{manager: manager}, IgnoreUnused()),
57
	)
58
59
	return manager
60
}
61
62
func NewChildManager(parent *Manager) *Manager {
63
	child := NewManager()
64
65
	child.parent = parent
66
67
	parent.child.Do(func(m children) children {
68
		m[child.id] = child
69
		return m
70
	})
71
72
	return child
73
}
74
75
func (m *Manager) Override(ctx context.Context, components ...Asset) {
76
	m.componentsLock.Lock()
77
	defer m.componentsLock.Unlock()
78
79
	for _, component := range components {
80
		logutil.FromContext(ctx).Debug("overriding component", "component", component.Name())
81
	}
82
83
	for _, component := range components {
84
		switch tComponent := component.(type) {
85
		case *serviceAsset:
86
			m.overrides = append(m.overrides, tComponent)
87
		case *configurationAsset:
88
			m.configuration = append([]*configurationAsset{tComponent}, m.configuration...)
89
		}
90
	}
91
}
92
93
func (m *Manager) AddComponent(ctx context.Context, components ...Asset) {
94
	m.componentsLock.Lock()
95
	defer m.componentsLock.Unlock()
96
97
	for _, component := range components {
98
		logutil.FromContext(ctx).Debug("adding component", "component", component.Name())
99
	}
100
101
	m._addComponent(ctx, components...)
102
}
103
104
func (m *Manager) _addComponent(_ context.Context, components ...Asset) {
105
	for _, component := range components {
106
		if component == nil {
107
			panic("component is nil")
108
		}
109
110
		switch tComponent := component.(type) {
111
		case *configurationAsset:
112
			m.configuration = append(m.configuration, tComponent)
113
		case *serviceAsset:
114
			m.components = append(m.components, tComponent)
115
		default:
116
			panic(throw.IllegalValue())
117
		}
118
	}
119
}
120
121
func (m *Manager) getConfigurationList() []*configurationAsset {
122
	if m.parent == nil {
123
		return m.configuration
124
	}
125
126
	return append(m.parent.getConfigurationList(), m.configuration...)
127
}
128
129
func (m *Manager) getServiceList() []*serviceAsset {
130
	if m.parent == nil {
131
		return append(append([]*serviceAsset{}, m.overrides...), m.components...)
132
	}
133
134
	middle := m.parent.getServiceList()
135
	// result of append will have next order:
136
	// ChildOverrides, ParentOverrides, ParentComponents, ChildComponents
137
	return append(
138
		append([]*serviceAsset{}, m.overrides...),
139
		append(middle, m.components...)...,
140
	)
141
142
}
143
144
func (m *Manager) inject(ctx context.Context, component *serviceAsset) {
145
	component.populateConfiguration(ctx, m.getConfigurationList())
146
	component.populateDependencies(ctx, m.getServiceList())
147
	component.status = AssetStatusInjected
148
}
149
150
func (m *Manager) Inject(ctx context.Context) error {
151
	m.componentsLock.Lock()
152
	defer m.componentsLock.Unlock()
153
154
	switch m.status {
155
	case ManagerCreated:
156
		m.status = ManagerInjected
157
	default:
158
		panic(throw.IllegalState())
159
	}
160
161
	allComponents := append(append([]*serviceAsset{}, m.overrides...), m.components...)
162
163
	for _, rawComponent := range allComponents {
164
		m.inject(ctx, rawComponent)
165
	}
166
167
	return nil
168
}
169
170
func (m *Manager) Init(ctx context.Context) error {
171
	managerCtx, _ := logutil.WithField(ctx, "component", "manager")
172
173
	m.componentsLock.Lock()
174
	defer m.componentsLock.Unlock()
175
176
	switch m.status {
177
	case ManagerInjected:
178
		m.status = ManagerInitialized
179
	default:
180
		panic(throw.IllegalState())
181
	}
182
183
	for _, rawComponent := range append(m.overrides, m.components...) {
184
		{
185
			managerCtxWithManager, wrapper := withInitScope(ctx, m)
186
			if err := rawComponent.CallInit(managerCtxWithManager); err != nil {
187
				return throw.W(err, "failed to initialize component", struct {
188
					Component string
189
				}{Component: rawComponent.Name()})
190
			}
191
			wrapper.Done(managerCtx)
192
		}
193
	}
194
195
	return nil
196
}
197
198
func (m *Manager) Start(ctx context.Context) error {
199
	managerCtx, _ := logutil.WithField(ctx, "component", "manager")
200
201
	m.componentsLock.Lock()
202
	defer m.componentsLock.Unlock()
203
204
	switch m.status {
205
	case ManagerInitialized:
206
		m.status = ManagerStarted
207
	default:
208
		panic(throw.WithDetails(throw.IllegalState(), struct {
209
			ExpectedState ManagerStatus
210
			Status        ManagerStatus
211
		}{ExpectedState: ManagerInitialized, Status: m.status}))
212
	}
213
214
	// get ordered list of components to start
215
	startStopOrder, err := m.buildExecutionOrder(ctx)
216
	if err != nil {
217
		return throw.W(err, "failed to build execution order")
218
	}
219
	m.startOrder = startStopOrder
220
221
	ctxWithManager, manager := withStartScope(ctx, m)
222
	for _, component := range startStopOrder {
223
		if err := component.CallStart(ctxWithManager); err != nil {
224
			return throw.W(err, "failed to start component")
225
		}
226
	}
227
228
	oldCleanup := m.onCleanup
229
	m.onCleanup = func() {
230
		manager.Done(managerCtx)
231
		oldCleanup()
232
	}
233
234
	return nil
235
}
236
237
func (m *Manager) Stop(ctx context.Context) error {
238
	managerCtx, log := logutil.WithField(ctx, "component", "manager")
239
240
	defer m._removeFromParent(managerCtx)
241
242
	m.componentsLock.Lock()
243
	defer m.componentsLock.Unlock()
244
245
	switch m.status {
246
	case ManagerStarted, ManagerStopped:
247
		m.status = ManagerStopped
248
	default:
249
		panic(throw.IllegalState())
250
	}
251
252
	for _, component := range collect.Reverse(m.startOrder) {
253
		if err := component.CallStop(ctx); err != nil {
254
			switch {
255
			case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
256
			default:
257
				err = throw.W(err, "failed to stop component, continuing...")
258
				log.Error("failed to stop component", "err", err)
259
			}
260
		}
261
	}
262
263
	log.Debug("stopping done")
264
265
	return nil
266
}
267
268
func (m *Manager) GracefulStop(ctx context.Context) error {
269
	managerCtx, _ := logutil.WithField(ctx, "component", "manager")
270
271
	defer m._removeFromParent(managerCtx)
272
273
	m.componentsLock.Lock()
274
	defer m.componentsLock.Unlock()
275
276
	switch m.status {
277
	case ManagerStarted:
278
		m.status = ManagerStopped
279
	default:
280
		panic(throw.IllegalState())
281
	}
282
283
	for _, component := range collect.Reverse(m.startOrder) {
284
		if err := component.CallGracefulStop(ctx); err != nil {
285
			return err
286
		}
287
	}
288
289
	m._removeFromParent(ctx)
290
291
	return nil
292
}
293
294
func (m *Manager) Destroy(ctx context.Context) error {
295
	managerCtx, log := logutil.WithField(ctx, "component", "manager")
296
297
	defer m._removeFromParent(managerCtx)
298
299
	m.componentsLock.Lock()
300
	defer m.componentsLock.Unlock()
301
302
	switch m.status {
303
	case ManagerStopped:
304
		m.status = ManagerDestroyed
305
	default:
306
		panic(throw.IllegalState())
307
	}
308
309
	for _, component := range collect.Reverse(m.startOrder) {
310
		if err := component.CallDestroy(ctx); err != nil {
311
			return throw.W(err, "failed to destroy component")
312
		}
313
	}
314
315
	log.Debug("destroying done")
316
317
	return nil
318
}
319
320
func (m *Manager) VerifyDestroyed(_ context.Context) error {
321
	childLen := syncutil.ApplyWithResult(m.child, func(m children) int {
322
		return len(m)
323
	})
324
	if childLen > 0 {
325
		return throw.New("child managers are not stopped")
326
	}
327
328
	for _, component := range m.startOrder {
329
		if component.status != AssetStatusDestroyed {
330
			return throw.New("component is not stopped", struct {
331
				Component string
332
			}{Component: component.Name()})
333
		}
334
	}
335
336
	return nil
337
}
338
339
func (m *Manager) HealthCheck(ctx context.Context) error {
340
	for _, component := range m.startOrder {
341
		if err := component.CallHealthCheck(ctx); err != nil {
342
			return throw.W(err, "failed to health check component")
343
		}
344
	}
345
	return nil
346
}
347
348
func (m *Manager) dot(result *strings.Builder, name string) {
349
	result.WriteString("digraph \"" + name + "\" {\n")
350
351
	for _, configuration := range m.configuration {
352
		result.WriteString("    ")
353
		result.WriteString("\"" + configuration.Name() + "\"")
354
		result.WriteString(" [shape=box];\n")
355
	}
356
357
	result.WriteByte('\n')
358
359
	currentComponents := set.NewSetFromListFunc(m.startOrder, func(inp *serviceAsset) string {
360
		return inp.Name()
361
	})
362
	externalComponents := set.NewSet[string](0)
363
364
	for _, component := range m.startOrder {
365
		result.WriteString("    ")
366
		result.WriteString("\"" + component.Name() + "\"")
367
		if component.root {
368
			result.WriteString(" [shape=ellipse, color=red];\n")
369
		} else {
370
			result.WriteString(" [shape=ellipse];\n")
371
		}
372
373
		if len(component.dependencies) == 0 {
374
			continue
375
		}
376
377
		result.WriteString("    ")
378
		result.WriteString("\"" + component.Name() + "\"")
379
		result.WriteString(" -> {")
380
		for pos, dependency := range component.dependencies {
381
			isLast := pos == len(component.dependencies)-1
382
383
			result.WriteString("\"" + dependency.Name() + "\"")
384
			if !isLast {
385
				result.WriteString("; ")
386
			}
387
388
			if !currentComponents.Has(dependency.Name()) {
389
				externalComponents.Add(dependency.Name())
390
			}
391
		}
392
		result.WriteString("};\n")
393
	}
394
395
	result.WriteByte('\n')
396
397
	externalComponents.Iterator().Apply(func(componentName string) {
398
		result.WriteString("    ")
399
		result.WriteString("\"" + componentName + "\"")
400
		result.WriteString(" [shape=ellipse, color=gray, style=filled];\n")
401
	})
402
403
	result.WriteString("}\n")
404
}
405
406
func (m *Manager) startStopDot(result *strings.Builder, name string) {
407
	result.WriteString("digraph \"order-" + name + "\" {\n")
408
409
	prevSvcName := ""
410
	for _, svc := range m.startOrder {
411
		result.WriteString("    ")
412
		result.WriteString("\"" + svc.Name() + "\"")
413
		result.WriteString(" [shape=ellipse];\n")
414
415
		if prevSvcName != "" {
416
			result.WriteString("    ")
417
			result.WriteString("\"" + prevSvcName + "\"")
418
			result.WriteString(" -> ")
419
			result.WriteString("\"" + svc.Name() + "\"")
420
			result.WriteString(";\n")
421
		}
422
423
		prevSvcName = svc.Name()
424
	}
425
426
	result.WriteString("}\n")
427
}
428
429
func (m *Manager) Dot(_ context.Context) string {
430
	result := strings.Builder{}
431
	m.dot(&result, "root")
432
	m.startStopDot(&result, "root")
433
434
	result.WriteString("\n")
435
436
	m.child.Apply(func(childMap children) {
437
		for _, child := range childMap {
438
			child.dot(&result, child.id.String())
439
			child.startStopDot(&result, child.id.String())
440
		}
441
	})
442
443
	return result.String()
444
}
445
446
func (m *Manager) _removeFromParent(ctx context.Context) {
447
	if m.parent == nil {
448
		return
449
	}
450
451
	logutil.FromContext(ctx).Debug("removing manager from parent", "manager", m.id)
452
453
	m.parent.child.Do(func(childMap children) children {
454
		if _, ok := childMap[m.id]; !ok {
455
			panic(throw.IllegalState())
456
		}
457
458
		delete(childMap, m.id)
459
460
		return childMap
461
	})
462
	m.parent = nil
463
}
464
465
func NewIntrospector(manager *Manager) *Introspector {
466
	return &Introspector{manager: manager}
467
}
468
469
type Introspector struct {
470
	manager *Manager
471
}
472
473
func (m *Introspector) Manager() {}
474
475
func (m *Introspector) Dot(ctx context.Context) string {
476
	return m.manager.Dot(ctx)
477
}
478

Source Files