github.com/grafana/synthetic-monitoring-agent@v0.64.2/internal/scraper/labels_test.go (about) 1 package scraper 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/require" 7 8 "github.com/grafana/synthetic-monitoring-agent/internal/model" 9 sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring" 10 ) 11 12 func makeLabels(pairs ...string) []sm.Label { 13 if len(pairs)%2 != 0 { 14 panic("labels: pairs must be even") 15 } 16 17 out := make([]sm.Label, 0, len(pairs)/2) 18 for i := 0; i < len(pairs); i += 2 { 19 out = append(out, sm.Label{Name: pairs[i], Value: pairs[i+1]}) 20 } 21 22 return out 23 } 24 25 func findLabel(t *testing.T, lps []labelPair, name string) labelPair { 26 t.Helper() 27 28 for _, lp := range lps { 29 if lp.name == name { 30 return lp 31 } 32 } 33 34 t.Fatalf("label %q not found in %v", name, lps) 35 36 return labelPair{} 37 } 38 39 func assertNoLabel(t *testing.T, lps []labelPair, name string) { 40 t.Helper() 41 42 for _, lp := range lps { 43 if lp.name == name { 44 t.Errorf("unexpected label %q found in %v", name, lps) 45 } 46 } 47 } 48 49 // TestBuildUserLabels_Prefixed_CheckOverridesProbe: probe sets env=dev, check sets env=prod. 50 // Result should be only label_env=prod (check wins, prefixed). 51 func TestBuildUserLabels_Prefixed_CheckOverridesProbe(t *testing.T) { 52 result := buildUserLabels( 53 makeLabels("env", "dev"), 54 makeLabels("env", "prod"), 55 sm.LabelMode_LABEL_MODE_PREFIXED, 56 ) 57 require.Len(t, result, 1) 58 require.Equal(t, "label_env", result[0].name) 59 require.Equal(t, "prod", result[0].value) 60 } 61 62 // TestBuildUserLabels_Prefixed_TwoDistinctLabels: probe has region=eu, check has env=prod. 63 // Both appear prefixed, probe label first. 64 func TestBuildUserLabels_Prefixed_TwoDistinctLabels(t *testing.T) { 65 result := buildUserLabels( 66 makeLabels("region", "eu"), 67 makeLabels("env", "prod"), 68 sm.LabelMode_LABEL_MODE_PREFIXED, 69 ) 70 require.Len(t, result, 2) 71 lp1 := findLabel(t, result, "label_region") 72 require.Equal(t, "eu", lp1.value) 73 lp2 := findLabel(t, result, "label_env") 74 require.Equal(t, "prod", lp2.value) 75 } 76 77 // TestBuildUserLabels_Prefixed_ReservedNotDropped: in PREFIXED mode, reserved names are NOT 78 // dropped. 79 func TestBuildUserLabels_Prefixed_ReservedNotDropped(t *testing.T) { 80 result := buildUserLabels( 81 nil, 82 makeLabels("probe", "myprobe"), 83 sm.LabelMode_LABEL_MODE_PREFIXED, 84 ) 85 require.Len(t, result, 1) 86 require.Equal(t, "label_probe", result[0].name) 87 require.Equal(t, "myprobe", result[0].value) 88 } 89 90 // TestBuildUserLabels_DualWrite_Basic: DUAL_WRITE emits all prefixed forms first, then all 91 // un-prefixed forms: [label_env, env]. The prefixed form comes first so that in the event 92 // of log stream label overflow, the form existing policies depend on is preserved. 93 func TestBuildUserLabels_DualWrite_Basic(t *testing.T) { 94 result := buildUserLabels( 95 nil, 96 makeLabels("env", "prod"), 97 sm.LabelMode_LABEL_MODE_DUAL_WRITE, 98 ) 99 require.Len(t, result, 2) 100 require.Equal(t, "label_env", result[0].name, "prefixed form must come first") 101 require.Equal(t, "prod", result[0].value) 102 require.Equal(t, "env", result[1].name, "un-prefixed form must come second") 103 require.Equal(t, "prod", result[1].value) 104 } 105 106 // TestBuildUserLabels_DualWrite_TwoLabels: two labels produce [label_a, label_b, a, b] — 107 // all prefixed first, then all un-prefixed. 108 func TestBuildUserLabels_DualWrite_TwoLabels(t *testing.T) { 109 result := buildUserLabels( 110 makeLabels("team", "sre"), 111 makeLabels("env", "prod"), 112 sm.LabelMode_LABEL_MODE_DUAL_WRITE, 113 ) 114 require.Len(t, result, 4) 115 // First half: all prefixed 116 require.Equal(t, "label_team", result[0].name) 117 require.Equal(t, "label_env", result[1].name) 118 // Second half: all un-prefixed 119 require.Equal(t, "team", result[2].name) 120 require.Equal(t, "env", result[3].name) 121 } 122 123 // TestBuildUserLabels_DualWrite_CheckOverridesProbe: probe env=dev, check env=prod. 124 // Both forms should carry the check's value. 125 func TestBuildUserLabels_DualWrite_CheckOverridesProbe(t *testing.T) { 126 result := buildUserLabels( 127 makeLabels("env", "dev"), 128 makeLabels("env", "prod"), 129 sm.LabelMode_LABEL_MODE_DUAL_WRITE, 130 ) 131 require.Len(t, result, 2) 132 require.Equal(t, "label_env", result[0].name) 133 require.Equal(t, "prod", result[0].value, "prefixed form should have check value") 134 require.Equal(t, "env", result[1].name) 135 require.Equal(t, "prod", result[1].value, "un-prefixed form should have check value") 136 } 137 138 // TestBuildUserLabels_DualWrite_ReservedNameDropped: a reserved name keeps its prefixed 139 // form (label_le, which cannot collide) but its un-prefixed form is dropped so it cannot 140 // overwrite an intrinsic per-series metric label. Non-reserved names keep both forms. 141 func TestBuildUserLabels_DualWrite_ReservedNameDropped(t *testing.T) { 142 result := buildUserLabels( 143 nil, 144 makeLabels("le", "user-value", "env", "prod"), 145 sm.LabelMode_LABEL_MODE_DUAL_WRITE, 146 ) 147 // [label_le, label_env, env] — the un-prefixed "le" is dropped. 148 require.Len(t, result, 3) 149 require.Equal(t, "label_le", result[0].name) 150 require.Equal(t, "label_env", result[1].name) 151 require.Equal(t, "env", result[2].name) 152 assertNoLabel(t, result, "le") 153 } 154 155 // TestBuildUserLabels_Unprefixed_Basic: single label env=prod produces only env=prod. 156 func TestBuildUserLabels_Unprefixed_Basic(t *testing.T) { 157 result := buildUserLabels( 158 nil, 159 makeLabels("env", "prod"), 160 sm.LabelMode_LABEL_MODE_UNPREFIXED, 161 ) 162 require.Len(t, result, 1) 163 require.Equal(t, "env", result[0].name) 164 require.Equal(t, "prod", result[0].value) 165 } 166 167 // TestBuildUserLabels_Unprefixed_ReservedNameDropped: a reserved name is dropped entirely 168 // in UNPREFIXED mode; non-reserved names pass through. 169 func TestBuildUserLabels_Unprefixed_ReservedNameDropped(t *testing.T) { 170 result := buildUserLabels( 171 nil, 172 makeLabels("le", "user-value", "env", "prod"), 173 sm.LabelMode_LABEL_MODE_UNPREFIXED, 174 ) 175 require.Len(t, result, 1) 176 require.Equal(t, "env", result[0].name) 177 assertNoLabel(t, result, "le") 178 } 179 180 // ── customMetricLabels tests ───────────────────────────────────────────── 181 182 // TestUserLabelsForExecution_Prefixed: PREFIXED mode returns nil — no user labels on execution metrics. 183 func TestUserLabelsForExecution_Prefixed(t *testing.T) { 184 result := customMetricLabels( 185 makeLabels("env", "prod"), 186 makeLabels("team", "platform"), 187 sm.LabelMode_LABEL_MODE_PREFIXED, 188 ) 189 require.Nil(t, result, "PREFIXED mode should return nil — execution metrics carry no user labels") 190 } 191 192 // TestUserLabelsForExecution_DualWrite: DUAL_WRITE returns only un-prefixed form. 193 func TestUserLabelsForExecution_DualWrite(t *testing.T) { 194 result := customMetricLabels( 195 nil, 196 makeLabels("env", "prod", "team", "platform"), 197 sm.LabelMode_LABEL_MODE_DUAL_WRITE, 198 ) 199 require.Len(t, result, 2, "should emit one entry per user label (un-prefixed only)") 200 findLabel(t, result, "env") 201 findLabel(t, result, "team") 202 assertNoLabel(t, result, "label_env") 203 assertNoLabel(t, result, "label_team") 204 } 205 206 // TestUserLabelsForExecution_Unprefixed: UNPREFIXED returns un-prefixed form. 207 func TestUserLabelsForExecution_Unprefixed(t *testing.T) { 208 result := customMetricLabels( 209 nil, 210 makeLabels("env", "prod"), 211 sm.LabelMode_LABEL_MODE_UNPREFIXED, 212 ) 213 require.Len(t, result, 1) 214 require.Equal(t, "env", result[0].name) 215 require.Equal(t, "prod", result[0].value) 216 } 217 218 // TestUserLabelsForExecution_ReservedNameDropped: reserved names are dropped from 219 // execution-metric labels in both DUAL_WRITE and UNPREFIXED, so a user label cannot 220 // overwrite an intrinsic per-series metric label such as `le`. 221 func TestUserLabelsForExecution_ReservedNameDropped(t *testing.T) { 222 for _, mode := range []sm.LabelMode{sm.LabelMode_LABEL_MODE_DUAL_WRITE, sm.LabelMode_LABEL_MODE_UNPREFIXED} { 223 result := customMetricLabels( 224 nil, 225 makeLabels("le", "user-value", "env", "prod"), 226 mode, 227 ) 228 require.Len(t, result, 1) 229 require.Equal(t, "env", result[0].name) 230 assertNoLabel(t, result, "le") 231 } 232 } 233 234 // TestUserLabelsForExecution_CheckOverridesProbe: check label wins over probe label for the same name. 235 func TestUserLabelsForExecution_CheckOverridesProbe(t *testing.T) { 236 result := customMetricLabels( 237 makeLabels("env", "staging"), 238 makeLabels("env", "prod"), 239 sm.LabelMode_LABEL_MODE_DUAL_WRITE, 240 ) 241 require.Len(t, result, 1) 242 require.Equal(t, "prod", result[0].value, "check label should override probe label") 243 } 244 245 // ── mergeLogLabels tests ───────────────────────────────────────────────────── 246 247 // TestMergeLogLabels_UserWinsSystemOrderingPreserved verifies that user labels 248 // override colliding system labels in place (user value wins), system labels 249 // keep their leading positions (so they survive stream-label overflow), and 250 // non-colliding user labels are appended after the system set. The input system 251 // slice must not be mutated. 252 func TestMergeLogLabels_UserWinsSystemOrderingPreserved(t *testing.T) { 253 system := []labelPair{ 254 {name: "probe", value: "p"}, 255 {name: "job", value: "sysjob"}, 256 {name: "region", value: "r"}, 257 } 258 user := []labelPair{ 259 {name: "job", value: "userjob"}, // collides with system "job" 260 {name: "env", value: "prod"}, // new 261 } 262 263 got := mergeLogLabels(system, user) 264 265 require.Equal(t, []labelPair{ 266 {name: "probe", value: "p"}, 267 {name: "job", value: "userjob"}, // user value wins, position preserved 268 {name: "region", value: "r"}, 269 {name: "env", value: "prod"}, // appended after the system labels 270 }, got) 271 272 // The input system slice must be left untouched. 273 require.Equal(t, "sysjob", system[1].value) 274 } 275 276 // TestMergeLogLabels_NoUserLabels verifies the system labels pass through 277 // unchanged when there are no user labels. 278 func TestMergeLogLabels_NoUserLabels(t *testing.T) { 279 system := []labelPair{ 280 {name: "probe", value: "p"}, 281 {name: "job", value: "sysjob"}, 282 } 283 284 got := mergeLogLabels(system, nil) 285 286 require.Equal(t, system, got) 287 } 288 289 // TestRegionLabelForMode pins the mode-conditional rename of the agent's 290 // region label — legacy PREFIXED tenants keep "region"; tenants that have 291 // started the migration receive "sm_region" (the reserved name) — and the 292 // invariant the API's alert expressions depend on: for migrated modes, 293 // sm_region is ALWAYS non-empty. A probe without a region gets the "unset" 294 // sentinel, because an empty label value is the same as an absent label in 295 // PromQL, and an absent sm_region classifies the series as pre-migration. 296 func TestRegionLabelForMode(t *testing.T) { 297 require.Equal(t, labelPair{name: "region", value: "EMEA"}, 298 regionLabelForMode(sm.LabelMode_LABEL_MODE_PREFIXED, "EMEA")) 299 require.Equal(t, labelPair{name: "sm_region", value: "EMEA"}, 300 regionLabelForMode(sm.LabelMode_LABEL_MODE_DUAL_WRITE, "EMEA")) 301 require.Equal(t, labelPair{name: "sm_region", value: "EMEA"}, 302 regionLabelForMode(sm.LabelMode_LABEL_MODE_UNPREFIXED, "EMEA")) 303 304 // Probe without a region: PREFIXED keeps the historical empty value 305 // (equivalent to an absent label), preserving legacy series identity. 306 require.Equal(t, labelPair{name: "region", value: ""}, 307 regionLabelForMode(sm.LabelMode_LABEL_MODE_PREFIXED, "")) 308 // Migrated modes must never emit an empty sm_region. 309 require.Equal(t, labelPair{name: "sm_region", value: "unset"}, 310 regionLabelForMode(sm.LabelMode_LABEL_MODE_DUAL_WRITE, "")) 311 require.Equal(t, labelPair{name: "sm_region", value: "unset"}, 312 regionLabelForMode(sm.LabelMode_LABEL_MODE_UNPREFIXED, "")) 313 } 314 315 // TestBuildUserLabels_SMRegionDropped pins that a user label named sm_region — 316 // the reserved name the agent's own region label migrates to — is dropped in 317 // enforced modes and cannot shadow the agent's value. 318 func TestBuildUserLabels_SMRegionDropped(t *testing.T) { 319 labels := []sm.Label{{Name: "sm_region", Value: "spoofed"}} 320 321 // DUAL_WRITE drops the un-prefixed reserved form but keeps the prefixed 322 // form, which cannot collide with the agent's sm_region. 323 require.Equal(t, 324 []labelPair{{name: "label_sm_region", value: "spoofed"}}, 325 buildUserLabels(nil, labels, sm.LabelMode_LABEL_MODE_DUAL_WRITE)) 326 require.Empty(t, buildUserLabels(nil, labels, sm.LabelMode_LABEL_MODE_UNPREFIXED)) 327 // In PREFIXED mode it is emitted with the prefix, which cannot collide. 328 require.Equal(t, 329 []labelPair{{name: "label_sm_region", value: "spoofed"}}, 330 buildUserLabels(nil, labels, sm.LabelMode_LABEL_MODE_PREFIXED)) 331 } 332 333 // TestBuildCheckInfoLabels_RegionlessProbe pins the sentinel end to end on 334 // sm_check_info: a migrated tenant's check running on a probe without a 335 // region still carries a non-empty sm_region (the generation marker the 336 // API's alert expressions select on), while a PREFIXED tenant's series keeps 337 // the historical empty region value. 338 func TestBuildCheckInfoLabels_RegionlessProbe(t *testing.T) { 339 s := Scraper{ 340 checkName: "ping", 341 check: model.Check{Check: sm.Check{Frequency: 60000}}, 342 probe: sm.Probe{Name: "regionless"}, 343 } 344 345 migrated := s.buildCheckInfoLabels(nil, nil, sm.LabelMode_LABEL_MODE_DUAL_WRITE) 346 require.Equal(t, "unset", migrated["sm_region"]) 347 require.NotContains(t, migrated, "region") 348 349 legacy := s.buildCheckInfoLabels(nil, nil, sm.LabelMode_LABEL_MODE_PREFIXED) 350 require.Equal(t, "", legacy["region"]) 351 require.NotContains(t, legacy, "sm_region") 352 }