github.com/influxdata/telegraf@v1.30.3/internal/snmp/table.go (about) 1 package snmp 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/gosnmp/gosnmp" 10 ) 11 12 // Table holds the configuration for a SNMP table. 13 type Table struct { 14 // Name will be the name of the measurement. 15 Name string 16 17 // Which tags to inherit from the top-level config. 18 InheritTags []string 19 20 // Adds each row's table index as a tag. 21 IndexAsTag bool 22 23 // Fields is the tags and values to look up. 24 Fields []Field `toml:"field"` 25 26 // OID for automatic field population. 27 // If provided, init() will populate Fields with all the table columns of the 28 // given OID. 29 Oid string 30 31 initialized bool 32 translator Translator 33 } 34 35 // RTable is the resulting table built from a Table. 36 type RTable struct { 37 // Name is the name of the field, copied from Table.Name. 38 Name string 39 // Time is the time the table was built. 40 Time time.Time 41 // Rows are the rows that were found, one row for each table OID index found. 42 Rows []RTableRow 43 } 44 45 // RTableRow is the resulting row containing all the OID values which shared 46 // the same index. 47 type RTableRow struct { 48 // Tags are all the Field values which had IsTag=true. 49 Tags map[string]string 50 // Fields are all the Field values which had IsTag=false. 51 Fields map[string]interface{} 52 } 53 54 // Init() builds & initializes the nested fields. 55 func (t *Table) Init(tr Translator) error { 56 //makes sure oid or name is set in config file 57 //otherwise snmp will produce metrics with an empty name 58 if t.Oid == "" && t.Name == "" { 59 return errors.New("SNMP table in config file is not named. One or both of the oid and name settings must be set") 60 } 61 62 if t.initialized { 63 return nil 64 } 65 66 t.translator = tr 67 if err := t.initBuild(); err != nil { 68 return err 69 } 70 71 secondaryIndexTablePresent := false 72 // initialize all the nested fields 73 for i := range t.Fields { 74 if err := t.Fields[i].Init(t.translator); err != nil { 75 return fmt.Errorf("initializing field %s: %w", t.Fields[i].Name, err) 76 } 77 if t.Fields[i].SecondaryIndexTable { 78 if secondaryIndexTablePresent { 79 return errors.New("only one field can be SecondaryIndexTable") 80 } 81 secondaryIndexTablePresent = true 82 } 83 } 84 85 t.initialized = true 86 return nil 87 } 88 89 // initBuild initializes the table if it has an OID configured. If so, the 90 // net-snmp tools will be used to look up the OID and auto-populate the table's 91 // fields. 92 func (t *Table) initBuild() error { 93 if t.Oid == "" { 94 return nil 95 } 96 97 _, _, oidText, fields, err := t.translator.SnmpTable(t.Oid) 98 if err != nil { 99 return err 100 } 101 102 if t.Name == "" { 103 t.Name = oidText 104 } 105 106 knownOIDs := map[string]bool{} 107 for _, f := range t.Fields { 108 knownOIDs[f.Oid] = true 109 } 110 for _, f := range fields { 111 if !knownOIDs[f.Oid] { 112 t.Fields = append(t.Fields, f) 113 } 114 } 115 116 return nil 117 } 118 119 // Build retrieves all the fields specified in the table and constructs the RTable. 120 func (t Table) Build(gs Connection, walk bool) (*RTable, error) { 121 rows := map[string]RTableRow{} 122 123 //translation table for secondary index (when performing join on two tables) 124 secIdxTab := make(map[string]string) 125 secGlobalOuterJoin := false 126 for i, f := range t.Fields { 127 if f.SecondaryIndexTable { 128 secGlobalOuterJoin = f.SecondaryOuterJoin 129 if i != 0 { 130 t.Fields[0], t.Fields[i] = t.Fields[i], t.Fields[0] 131 } 132 break 133 } 134 } 135 136 tagCount := 0 137 for _, f := range t.Fields { 138 if f.IsTag { 139 tagCount++ 140 } 141 142 if len(f.Oid) == 0 { 143 return nil, fmt.Errorf("cannot have empty OID on field %s", f.Name) 144 } 145 var oid string 146 if f.Oid[0] == '.' { 147 oid = f.Oid 148 } else { 149 // make sure OID has "." because the BulkWalkAll results do, and the prefix needs to match 150 oid = "." + f.Oid 151 } 152 153 // ifv contains a mapping of table OID index to field value 154 ifv := map[string]interface{}{} 155 156 if !walk { 157 // This is used when fetching non-table fields. Fields configured a the top 158 // scope of the plugin. 159 // We fetch the fields directly, and add them to ifv as if the index were an 160 // empty string. This results in all the non-table fields sharing the same 161 // index, and being added on the same row. 162 if pkt, err := gs.Get([]string{oid}); err != nil { 163 if errors.Is(err, gosnmp.ErrUnknownSecurityLevel) { 164 return nil, errors.New("unknown security level (sec_level)") 165 } else if errors.Is(err, gosnmp.ErrUnknownUsername) { 166 return nil, errors.New("unknown username (sec_name)") 167 } else if errors.Is(err, gosnmp.ErrWrongDigest) { 168 return nil, errors.New("wrong digest (auth_protocol, auth_password)") 169 } else if errors.Is(err, gosnmp.ErrDecryption) { 170 return nil, errors.New("decryption error (priv_protocol, priv_password)") 171 } 172 return nil, fmt.Errorf("performing get on field %s: %w", f.Name, err) 173 } else if pkt != nil && len(pkt.Variables) > 0 && pkt.Variables[0].Type != gosnmp.NoSuchObject && pkt.Variables[0].Type != gosnmp.NoSuchInstance { 174 ent := pkt.Variables[0] 175 fv, err := f.Convert(ent) 176 if err != nil { 177 return nil, fmt.Errorf("converting %q (OID %s) for field %s: %w", ent.Value, ent.Name, f.Name, err) 178 } 179 ifv[""] = fv 180 } 181 } else { 182 err := gs.Walk(oid, func(ent gosnmp.SnmpPDU) error { 183 if len(ent.Name) <= len(oid) || ent.Name[:len(oid)+1] != oid+"." { 184 return &walkError{} // break the walk 185 } 186 187 idx := ent.Name[len(oid):] 188 if f.OidIndexSuffix != "" { 189 if !strings.HasSuffix(idx, f.OidIndexSuffix) { 190 // this entry doesn't match our OidIndexSuffix. skip it 191 return nil 192 } 193 idx = idx[:len(idx)-len(f.OidIndexSuffix)] 194 } 195 if f.OidIndexLength != 0 { 196 i := f.OidIndexLength + 1 // leading separator 197 idx = strings.Map(func(r rune) rune { 198 if r == '.' { 199 i-- 200 } 201 if i < 1 { 202 return -1 203 } 204 return r 205 }, idx) 206 } 207 208 // snmptranslate table field value here 209 if f.Translate { 210 if entOid, ok := ent.Value.(string); ok { 211 _, _, oidText, _, err := t.translator.SnmpTranslate(entOid) 212 if err == nil { 213 // If no error translating, the original value for ent.Value should be replaced 214 ent.Value = oidText 215 } 216 } 217 } 218 219 fv, err := f.Convert(ent) 220 if err != nil { 221 return &walkError{ 222 msg: fmt.Sprintf("converting %q (OID %s) for field %s", ent.Value, ent.Name, f.Name), 223 err: err, 224 } 225 } 226 ifv[idx] = fv 227 return nil 228 }) 229 if err != nil { 230 // Our callback always wraps errors in a walkError. 231 // If this error isn't a walkError, we know it's not 232 // from the callback 233 var walkErr *walkError 234 if !errors.As(err, &walkErr) { 235 return nil, fmt.Errorf("performing bulk walk for field %s: %w", f.Name, err) 236 } 237 } 238 } 239 240 for idx, v := range ifv { 241 if f.SecondaryIndexUse { 242 if newidx, ok := secIdxTab[idx]; ok { 243 idx = newidx 244 } else { 245 if !secGlobalOuterJoin && !f.SecondaryOuterJoin { 246 continue 247 } 248 idx = ".Secondary" + idx 249 } 250 } 251 rtr, ok := rows[idx] 252 if !ok { 253 rtr = RTableRow{} 254 rtr.Tags = map[string]string{} 255 rtr.Fields = map[string]interface{}{} 256 rows[idx] = rtr 257 } 258 if t.IndexAsTag && idx != "" { 259 if idx[0] == '.' { 260 idx = idx[1:] 261 } 262 rtr.Tags["index"] = idx 263 } 264 // don't add an empty string 265 if vs, ok := v.(string); !ok || vs != "" { 266 if f.IsTag { 267 if ok { 268 rtr.Tags[f.Name] = vs 269 } else { 270 rtr.Tags[f.Name] = fmt.Sprintf("%v", v) 271 } 272 } else { 273 rtr.Fields[f.Name] = v 274 } 275 if f.SecondaryIndexTable { 276 //indexes are stored here with prepending "." so we need to add them if needed 277 var vss string 278 if ok { 279 vss = "." + vs 280 } else { 281 vss = fmt.Sprintf(".%v", v) 282 } 283 if idx[0] == '.' { 284 secIdxTab[vss] = idx 285 } else { 286 secIdxTab[vss] = "." + idx 287 } 288 } 289 } 290 } 291 } 292 293 rt := RTable{ 294 Name: t.Name, 295 Time: time.Now(), //TODO record time at start 296 Rows: make([]RTableRow, 0, len(rows)), 297 } 298 for _, r := range rows { 299 rt.Rows = append(rt.Rows, r) 300 } 301 return &rt, nil 302 } 303 304 type walkError struct { 305 msg string 306 err error 307 } 308 309 func (e *walkError) Error() string { 310 return e.msg 311 } 312 313 func (e *walkError) Unwrap() error { 314 return e.err 315 }