master
go 131 lines 3.62 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package program
4
5 import (
6 "errors"
7 "fmt"
8 "strings"
9 )
10
11 // PromotionMode defines how non-identity chart labels are selected.
12 type PromotionMode string
13
14 const (
15 PromotionModeAutoIntersection PromotionMode = "auto_intersection"
16 PromotionModeExplicitIntersection PromotionMode = "explicit_intersection"
17 )
18
19 // LabelPolicy describes chart-label promotion and conflict behavior in IR.
20 type LabelPolicy struct {
21 Mode PromotionMode
22
23 // PromoteKeys is explicit allowlist for PromotionModeExplicitIntersection.
24 PromoteKeys []string
25
26 // Exclusions contains compile-resolved label keys that must never be
27 // promoted (e.g. selector-constrained keys, dynamic dimension-name keys).
28 Exclusions LabelExclusions
29
30 // Precedence controls deterministic same-key merge behavior.
31 Precedence LabelPrecedence
32 }
33
34 // LabelExclusions stores precomputed exclusion sets used by chart labeling.
35 type LabelExclusions struct {
36 SelectorConstrainedKeys []string
37 DimensionKeyLabels []string
38 }
39
40 // LabelPrecedence encodes the selected conflict policy.
41 type LabelPrecedence struct {
42 SelectedOverInstance bool
43 InstanceOverJob bool
44 ReservedImmutable bool
45 }
46
47 // DefaultLabelPrecedence returns the phase-1 conflict policy baseline.
48 func DefaultLabelPrecedence() LabelPrecedence {
49 return LabelPrecedence{
50 SelectedOverInstance: true,
51 InstanceOverJob: true,
52 ReservedImmutable: true,
53 }
54 }
55
56 func validateLabelPolicy(policy LabelPolicy) error {
57 switch policy.Mode {
58 case PromotionModeAutoIntersection, PromotionModeExplicitIntersection:
59 return nil
60 default:
61 return fmt.Errorf("invalid promotion mode %q", policy.Mode)
62 }
63 }
64
65 func validateInstanceLabelSelectors(selectors []InstanceLabelSelector) error {
66 if len(selectors) == 0 {
67 return nil
68 }
69
70 hasPositive := false
71 var errs []error
72 for i, selector := range selectors {
73 switch {
74 case selector.IncludeAll:
75 if selector.Exclude || selector.Key != "" {
76 errs = append(errs, fmt.Errorf("instance selector[%d]: include-all selector must not set exclude or key", i))
77 continue
78 }
79 hasPositive = true
80 case selector.Exclude:
81 if selector.Key == "" {
82 errs = append(errs, fmt.Errorf("instance selector[%d]: exclude selector key is required", i))
83 continue
84 }
85 if strings.TrimSpace(selector.Key) != selector.Key {
86 errs = append(errs, fmt.Errorf("instance selector[%d]: exclude selector key must be trimmed", i))
87 }
88 case selector.Key != "":
89 if strings.TrimSpace(selector.Key) != selector.Key {
90 errs = append(errs, fmt.Errorf("instance selector[%d]: selector key must be trimmed", i))
91 }
92 hasPositive = true
93 default:
94 errs = append(errs, fmt.Errorf("instance selector[%d]: selector is empty", i))
95 }
96 }
97
98 if !hasPositive {
99 errs = append(errs, fmt.Errorf("instance selectors must include at least one positive selector"))
100 }
101 return errors.Join(errs...)
102 }
103
104 // InstanceLabelSelector is a normalized token from instances.by_labels.
105 type InstanceLabelSelector struct {
106 // IncludeAll corresponds to token "*".
107 IncludeAll bool
108 // Exclude corresponds to token "!<key>".
109 Exclude bool
110 // Key is used by explicit include or exclude token.
111 Key string
112 }
113
114 func (p LabelPolicy) clone() LabelPolicy {
115 out := p
116 out.PromoteKeys = append([]string(nil), p.PromoteKeys...)
117 out.Exclusions = p.Exclusions.clone()
118 out.Precedence = p.Precedence
119 return out
120 }
121
122 func (e LabelExclusions) clone() LabelExclusions {
123 out := e
124 out.SelectorConstrainedKeys = append([]string(nil), e.SelectorConstrainedKeys...)
125 out.DimensionKeyLabels = append([]string(nil), e.DimensionKeyLabels...)
126 return out
127 }
128
129 func (s InstanceLabelSelector) clone() InstanceLabelSelector {
130 return s
131 }