@cryptotaxi247 / netdata / commits / 4f5d6e902

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) {