fix(go.d/ddsnmp): fix match_pattern regex behavior in metadata and metric collection (#20814)
Ilya Mashchenko committed
Aug 13, 2025 at 00:07 UTC
4f5d6e902dab6acaea6a434acd3de7666a6fcdb4
3 files changed
+233
-4
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collectors_meta.go
+8
-2
@@ -325,12 +325,18 @@ func (dc *deviceMetadataCollector) processSymbolValue(cfg ddprofiledefinition.Sy
325
if sm := cfg.ExtractValueCompiled.FindStringSubmatch(val); len(sm) > 1 {
326
val = sm[1]
327
}
328
+ // Note: If extract_value doesn't match, we still use the original value
329
+ // This is intentional as extract_value is for extracting a portion of the value
330
}
331
332
if cfg.MatchPatternCompiled != nil {
331
- if sm := cfg.MatchPatternCompiled.FindStringSubmatch(val); len(sm) > 0 {
332
- val = replaceSubmatches(cfg.MatchValue, sm)
333
+ sm := cfg.MatchPatternCompiled.FindStringSubmatch(val)
334
+ if len(sm) == 0 {
335
+ // Pattern didn't match - return empty string to indicate no match
336
+ // When match_pattern is specified, we only use the value if it matches
337
+ return "", nil
338
}
339
+ val = replaceSubmatches(cfg.MatchValue, sm)
340
}
341
342
if v, ok := cfg.Mapping[val]; ok {
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collectors_meta_test.go
+220
@@ -1213,6 +1213,226 @@ func TestDeviceMetadataCollector_Collect(t *testing.T) {
1213
},
1214
expectedError: false, // Should continue with partial data
1215
},
1216
+ "os_name with multiple symbols and match_pattern fallback": {
1217
+ profile: &ddsnmp.Profile{
1218
+ Definition: &ddprofiledefinition.ProfileDefinition{
1219
+ Metadata: ddprofiledefinition.MetadataConfig{
1220
+ "device": ddprofiledefinition.MetadataResourceConfig{
1221
+ Fields: ddprofiledefinition.ListMap[ddprofiledefinition.MetadataField]{
1222
+ "vendor": {
1223
+ Value: "Cisco",
1224
+ },
1225
+ "os_name": {
1226
+ Symbols: []ddprofiledefinition.SymbolConfig{
1227
+ {
1228
+ OID: "1.3.6.1.2.1.1.1.0",
1229
+ Name: "sysDescr",
1230
+ MatchPatternCompiled: mustCompileRegex(`Cisco Internetwork Operating System Software`),
1231
+ MatchValue: "IOS",
1232
+ },
1233
+ {
1234
+ OID: "1.3.6.1.2.1.1.1.0",
1235
+ Name: "sysDescr",
1236
+ MatchPatternCompiled: mustCompileRegex(`Cisco IOS Software`),
1237
+ MatchValue: "IOS",
1238
+ },
1239
+ {
1240
+ OID: "1.3.6.1.2.1.1.1.0",
1241
+ Name: "sysDescr",
1242
+ MatchPatternCompiled: mustCompileRegex(`Cisco NX-OS`),
1243
+ MatchValue: "NXOS",
1244
+ },
1245
+ {
1246
+ OID: "1.3.6.1.2.1.1.1.0",
1247
+ Name: "sysDescr",
1248
+ MatchPatternCompiled: mustCompileRegex(`Cisco IOS XR`),
1249
+ MatchValue: "IOSXR",
1250
+ },
1251
+ },
1252
+ },
1253
+ },
1254
+ },
1255
+ },
1256
+ },
1257
+ },
1258
+ setupMock: func(m *snmpmock.MockHandler) {
1259
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
1260
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.1.1.0"}).Return(
1261
+ &gosnmp.SnmpPacket{
1262
+ Variables: []gosnmp.SnmpPDU{
1263
+ {
1264
+ Name: "1.3.6.1.2.1.1.1.0",
1265
+ Type: gosnmp.OctetString,
1266
+ Value: []byte("Cisco NX-OS(tm) m9100, Software (m9100-s2ek9-mz), Version 4.1(1c), RELEASE SOFTWARE Copyright (c) 2002-2008 by Cisco Systems, Inc. Compiled 11/24/2008 18:00:00"),
1267
+ },
1268
+ },
1269
+ }, nil,
1270
+ )
1271
+ },
1272
+ expectedResult: map[string]string{
1273
+ "vendor": "Cisco",
1274
+ "os_name": "NXOS", // Should match the third pattern and use its match_value
1275
+ },
1276
+ expectedError: false,
1277
+ },
1278
+ "os_name with multiple symbols - no match fallback": {
1279
+ profile: &ddsnmp.Profile{
1280
+ Definition: &ddprofiledefinition.ProfileDefinition{
1281
+ Metadata: ddprofiledefinition.MetadataConfig{
1282
+ "device": ddprofiledefinition.MetadataResourceConfig{
1283
+ Fields: ddprofiledefinition.ListMap[ddprofiledefinition.MetadataField]{
1284
+ "vendor": {
1285
+ Value: "Juniper",
1286
+ },
1287
+ "os_name": {
1288
+ Symbols: []ddprofiledefinition.SymbolConfig{
1289
+ {
1290
+ OID: "1.3.6.1.2.1.1.1.0",
1291
+ Name: "sysDescr",
1292
+ MatchPatternCompiled: mustCompileRegex(`Cisco IOS Software`),
1293
+ MatchValue: "IOS",
1294
+ },
1295
+ {
1296
+ OID: "1.3.6.1.2.1.1.1.0",
1297
+ Name: "sysDescr",
1298
+ MatchPatternCompiled: mustCompileRegex(`Cisco NX-OS`),
1299
+ MatchValue: "NXOS",
1300
+ },
1301
+ },
1302
+ },
1303
+ },
1304
+ },
1305
+ },
1306
+ },
1307
+ },
1308
+ setupMock: func(m *snmpmock.MockHandler) {
1309
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
1310
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.1.1.0"}).Return(
1311
+ &gosnmp.SnmpPacket{
1312
+ Variables: []gosnmp.SnmpPDU{
1313
+ {
1314
+ Name: "1.3.6.1.2.1.1.1.0",
1315
+ Type: gosnmp.OctetString,
1316
+ Value: []byte("Juniper Networks, Inc. mx960 internet router, kernel JUNOS 12.3R3.4"),
1317
+ },
1318
+ },
1319
+ }, nil,
1320
+ )
1321
+ },
1322
+ expectedResult: map[string]string{
1323
+ "vendor": "Juniper",
1324
+ // os_name should not be set since no patterns match
1325
+ },
1326
+ expectedError: false,
1327
+ },
1328
+ "os_name with single symbol and match_pattern - no match": {
1329
+ profile: &ddsnmp.Profile{
1330
+ Definition: &ddprofiledefinition.ProfileDefinition{
1331
+ Metadata: ddprofiledefinition.MetadataConfig{
1332
+ "device": ddprofiledefinition.MetadataResourceConfig{
1333
+ Fields: ddprofiledefinition.ListMap[ddprofiledefinition.MetadataField]{
1334
+ "vendor": {
1335
+ Value: "Generic",
1336
+ },
1337
+ "os_name": {
1338
+ Symbol: ddprofiledefinition.SymbolConfig{
1339
+ OID: "1.3.6.1.2.1.1.1.0",
1340
+ Name: "sysDescr",
1341
+ MatchPatternCompiled: mustCompileRegex(`Cisco IOS Software`),
1342
+ MatchValue: "IOS",
1343
+ },
1344
+ },
1345
+ },
1346
+ },
1347
+ },
1348
+ },
1349
+ },
1350
+ setupMock: func(m *snmpmock.MockHandler) {
1351
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
1352
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.1.1.0"}).Return(
1353
+ &gosnmp.SnmpPacket{
1354
+ Variables: []gosnmp.SnmpPDU{
1355
+ {
1356
+ Name: "1.3.6.1.2.1.1.1.0",
1357
+ Type: gosnmp.OctetString,
1358
+ Value: []byte("Some other device description"),
1359
+ },
1360
+ },
1361
+ }, nil,
1362
+ )
1363
+ },
1364
+ expectedResult: map[string]string{
1365
+ "vendor": "Generic",
1366
+ // os_name should not be set since pattern doesn't match
1367
+ },
1368
+ expectedError: false,
1369
+ },
1370
+ "mixed fields with match_pattern and extract_value": {
1371
+ profile: &ddsnmp.Profile{
1372
+ Definition: &ddprofiledefinition.ProfileDefinition{
1373
+ Metadata: ddprofiledefinition.MetadataConfig{
1374
+ "device": ddprofiledefinition.MetadataResourceConfig{
1375
+ Fields: ddprofiledefinition.ListMap[ddprofiledefinition.MetadataField]{
1376
+ "vendor": {
1377
+ Value: "Cisco",
1378
+ },
1379
+ "version": {
1380
+ Symbol: ddprofiledefinition.SymbolConfig{
1381
+ OID: "1.3.6.1.2.1.1.1.0",
1382
+ Name: "sysDescr",
1383
+ ExtractValueCompiled: mustCompileRegex(`Version\s+([a-zA-Z0-9.()\[\]]+)`),
1384
+ },
1385
+ },
1386
+ "model": {
1387
+ Symbol: ddprofiledefinition.SymbolConfig{
1388
+ OID: "1.3.6.1.2.1.1.1.0",
1389
+ Name: "sysDescr",
1390
+ ExtractValueCompiled: mustCompileRegex(`Software\s+\(([-a-zA-Z0-9_ ]+)\)`),
1391
+ },
1392
+ },
1393
+ "os_name": {
1394
+ Symbols: []ddprofiledefinition.SymbolConfig{
1395
+ {
1396
+ OID: "1.3.6.1.2.1.1.1.0",
1397
+ Name: "sysDescr",
1398
+ MatchPatternCompiled: mustCompileRegex(`Cisco IOS Software`),
1399
+ MatchValue: "IOS",
1400
+ },
1401
+ {
1402
+ OID: "1.3.6.1.2.1.1.1.0",
1403
+ Name: "sysDescr",
1404
+ MatchPatternCompiled: mustCompileRegex(`Cisco IOS XR Software`),
1405
+ MatchValue: "IOSXR",
1406
+ },
1407
+ },
1408
+ },
1409
+ },
1410
+ },
1411
+ },
1412
+ },
1413
+ },
1414
+ setupMock: func(m *snmpmock.MockHandler) {
1415
+ m.EXPECT().MaxOids().Return(10).AnyTimes()
1416
+ m.EXPECT().Get([]string{"1.3.6.1.2.1.1.1.0"}).Return(
1417
+ &gosnmp.SnmpPacket{
1418
+ Variables: []gosnmp.SnmpPDU{
1419
+ {
1420
+ Name: "1.3.6.1.2.1.1.1.0",
1421
+ Type: gosnmp.OctetString,
1422
+ Value: []byte("Cisco IOS XR Software (Cisco ASR9K Series), Version 4.2.3[Default] Copyright (c) 2013 by Cisco Systems, Inc."),
1423
+ },
1424
+ },
1425
+ }, nil,
1426
+ )
1427
+ },
1428
+ expectedResult: map[string]string{
1429
+ "vendor": "Cisco",
1430
+ "version": "4.2.3[Default]", // Extracted using extract_value
1431
+ "model": "Cisco ASR9K Series", // Extracted using extract_value
1432
+ "os_name": "IOSXR", // Matched second pattern
1433
+ },
1434
+ expectedError: false,
1435
+ },
1436
}
1437
1438
for name, tc := range tests {
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/value_processor.go
+5
-2
@@ -102,9 +102,12 @@ func (p *stringValueProcessor) processValue(sym ddprofiledefinition.SymbolConfig
102
}
103
104
if sym.MatchPatternCompiled != nil {
105
- if sm := sym.MatchPatternCompiled.FindStringSubmatch(s); len(sm) > 0 {
106
- s = replaceSubmatches(sym.MatchValue, sm)
105
+ sm := sym.MatchPatternCompiled.FindStringSubmatch(s)
106
+ if len(sm) == 0 {
107
+ // Pattern didn't match - cannot extract expected value
108
+ return 0, fmt.Errorf("match_pattern '%s' did not match value '%s'", sym.MatchPattern, s)
109
}
110
+ s = replaceSubmatches(sym.MatchValue, sm)
111
}
112
113
if v, ok := sym.Mapping[s]; ok && isInt(v) {