@@ -17,7 +17,9 @@ import (
1717 "sync"
1818 "time"
1919
20+ devicepb "github.com/kentik/api-schema-public/gen/go/kentik/device/v202504beta2"
2021 tagging "github.com/kentik/api-schema-public/gen/go/kentik/enrichments/enumerations/v202601alpha1"
22+ interfacepb "github.com/kentik/api-schema-public/gen/go/kentik/interface/v202108alpha1"
2123 synthetics "github.com/kentik/api-schema-public/gen/go/kentik/synthetics/v202309"
2224 "github.com/kentik/ktranslate"
2325 "github.com/kentik/ktranslate/pkg/eggs/logger"
@@ -32,12 +34,14 @@ import (
3234)
3335
3436const (
35- CACHE_TIME_DEVICE = 1 * time .Hour
36- API_TIMEOUT = 60 * time .Second
37- HTTP_USER_AGENT = "User-Agent"
38- API_EMAIL_HEADER = "X-CH-Auth-Email"
39- API_PASSWORD_HEADER = "X-CH-Auth-API-Token"
40- MIN_TIME_BETWEEN_SYNTH_CHECKS = 60 * time .Second
37+ CACHE_TIME_DEVICE = 1 * time .Hour
38+ API_TIMEOUT = 60 * time .Second
39+ HTTP_USER_AGENT = "User-Agent"
40+ API_EMAIL_HEADER = "X-CH-Auth-Email"
41+ API_PASSWORD_HEADER = "X-CH-Auth-API-Token"
42+ MIN_TIME_BETWEEN_SYNTH_CHECKS = 60 * time .Second
43+ KT_API_LOAD_INTERFACES = "KT_API_LOAD_INTERFACES"
44+ KT_INTERFACE_LOOKUP_TEXT_FILTER = "KT_INTERFACE_LOOKUP_TEXT_FILTER"
4145)
4246
4347var (
@@ -59,10 +63,13 @@ type KentikApi struct {
5963 setTime time.Time
6064 apiTimeout time.Duration
6165 synClient synthetics.SyntheticsAdminServiceClient
66+ deviceClient devicepb.DeviceServiceClient
67+ interfaceClient interfacepb.InterfaceServiceClient
6268 mux sync.RWMutex
6369 lastSynth time.Time
6470 config * ktranslate.Config
6571 tagLookupClient tagging.EnumerationsAdminServiceClient
72+ loadInterfaces bool
6673}
6774
6875func NewKentikApi (ctx context.Context , log logger.ContextL , cfg * ktranslate.Config ) (* KentikApi , error ) {
@@ -85,11 +92,12 @@ func NewKentikApi(ctx context.Context, log logger.ContextL, cfg *ktranslate.Conf
8592 client := & http.Client {Transport : tr , Timeout : apiTimeout }
8693
8794 kapi := & KentikApi {
88- ContextL : log ,
89- tr : tr ,
90- client : client ,
91- apiTimeout : apiTimeout ,
92- config : cfg ,
95+ ContextL : log ,
96+ tr : tr ,
97+ client : client ,
98+ apiTimeout : apiTimeout ,
99+ config : cfg ,
100+ loadInterfaces : kt .LookupEnvBool (KT_API_LOAD_INTERFACES , false ),
93101 }
94102
95103 // Now, check to see if synthetics API works.
@@ -98,16 +106,13 @@ func NewKentikApi(ctx context.Context, log logger.ContextL, cfg *ktranslate.Conf
98106 return nil , err
99107 }
100108
101- // check api works and also pre-populate cache.
102- err = kapi .getDevices (ctx )
103-
104109 // Drop cache every hour to keep up to date
105110 go kapi .manageCache (ctx )
106111
107112 return kapi , err
108113}
109114
110- func (api * KentikApi ) getDeviceInfo (ctx context.Context , apiUrl string , apiEmail string , apiToken string ) ([]byte , error ) {
115+ func (api * KentikApi ) callRestAPI (ctx context.Context , apiUrl string , apiEmail string , apiToken string ) ([]byte , error ) {
111116 if apiEmail == "" {
112117 if v := api .config .API .DeviceFile ; v != "" {
113118 api .Infof ("Reading devices from local file: %s" , v )
@@ -176,7 +181,7 @@ func (api *KentikApi) UpdateTests(ctx context.Context) {
176181 }
177182
178183 go func () {
179- err := api .getSynthInfo (ctx )
184+ err := api .getSynthAndDeviceInfo (ctx )
180185 if err != nil {
181186 api .Errorf ("Cannot get API synth on demand: %v" , err )
182187 }
@@ -238,66 +243,6 @@ func (api *KentikApi) GetDevice(cid kt.Cid, did kt.DeviceID) *kt.Device {
238243 return nil
239244}
240245
241- func (api * KentikApi ) getInterfaces (ctx context.Context , did kt.DeviceID , info ktranslate.KentikCred ) ([]kt.Interface , error ) {
242- res , err := api .getDeviceInfo (ctx , fmt .Sprintf (api .config .APIBaseURL + "/api/internal/device/%d/interfaces" , did ), info .APIEmail , info .APIToken )
243- if err != nil {
244- return nil , err
245- }
246- interfaces := []kt.Interface {}
247- err = json .Unmarshal (res , & interfaces )
248- return interfaces , err
249- }
250-
251- func (api * KentikApi ) getDevices (ctx context.Context ) error {
252- stime := time .Now ()
253- resDev := map [kt.Cid ]kt.Devices {}
254- num := 0
255- for _ , info := range api .config .KentikCreds {
256- res , err := api .getDeviceInfo (ctx , api .config .APIBaseURL + "/api/internal/devices" , info .APIEmail , info .APIToken )
257- if err != nil {
258- return err
259- }
260- var devices kt.DeviceList
261- err = json .Unmarshal (res , & devices )
262- if err != nil {
263- return err
264- }
265-
266- for _ , device := range devices .Devices {
267- myd := device
268- if _ , ok := resDev [device .CompanyID ]; ! ok {
269- resDev [device .CompanyID ] = map [kt.DeviceID ]* kt.Device {}
270- }
271- myd .Interfaces = map [kt.IfaceID ]kt.Interface {}
272- interfaces , err := api .getInterfaces (ctx , device .ID , info )
273- if err != nil {
274- api .Errorf ("Cannot get interfaces for %v: %v" , device .Name , err )
275- } else {
276- for _ , intf := range interfaces {
277- intfl := intf // Should this be a pointer?
278- myd .Interfaces [intf .SnmpID ] = intfl
279- }
280- }
281- resDev [device.CompanyID ][device.ID ] = & myd
282- num ++
283- }
284-
285- api .Infof ("Loaded %d Kentik devices via API for %s" , len (devices .Devices ), info .APIEmail )
286- }
287-
288- api .setTime = time .Now ()
289- api .Infof ("Loaded %d Kentik devices via API in %v" , num , api .setTime .Sub (stime ))
290- api .devices = resDev
291-
292- // Now pull in site info for these devices.
293- err := api .getSites (ctx )
294- if err != nil {
295- return err
296- }
297-
298- return nil
299- }
300-
301246func (api * KentikApi ) getSites (ctx context.Context ) error {
302247 stime := time .Now ()
303248 num := 0
@@ -307,7 +252,7 @@ func (api *KentikApi) getSites(ctx context.Context) error {
307252
308253 for _ , info := range api .config .KentikCreds {
309254 for _ , version := range versions {
310- res , err := api .getDeviceInfo (ctx , fmt .Sprintf ("%s/site/%s/sites" , api .config .GRPCBaseURL , version ), info .APIEmail , info .APIToken )
255+ res , err := api .callRestAPI (ctx , fmt .Sprintf ("%s/site/%s/sites" , api .config .GRPCBaseURL , version ), info .APIEmail , info .APIToken )
311256 if err != nil {
312257 api .Warnf ("Skipping site version %s" , version )
313258 continue
@@ -383,11 +328,7 @@ func (api *KentikApi) manageCache(ctx context.Context) {
383328 for {
384329 select {
385330 case _ = <- checkTicker .C :
386- err := api .getDevices (ctx )
387- if err != nil {
388- api .Errorf ("Cannot get API devices: %v" , err )
389- }
390- err = api .getSynthInfo (ctx )
331+ err := api .getSynthAndDeviceInfo (ctx )
391332 if err != nil {
392333 api .Errorf ("Cannot get API synth: %v" , err )
393334 }
@@ -436,10 +377,12 @@ func (api *KentikApi) connectSynthAndLookup(ctxIn context.Context) error {
436377 }
437378
438379 api .Infof ("Connecting to API server at %s" , address )
380+ maxMsgSize := 50 * 1024 * 1024 // 50 MB
439381 opts := []grpc.DialOption {
440382 grpc .WithUserAgent (userAgent ()),
441383 grpc .WithKeepaliveParams (api .getClientKeepAlive ()),
442384 grpc .WithTransportCredentials (creds ),
385+ grpc .WithDefaultCallOptions (grpc .MaxCallRecvMsgSize (maxMsgSize )),
443386 }
444387
445388 conn , err := grpc .NewClient (address , opts ... )
@@ -455,10 +398,20 @@ func (api *KentikApi) connectSynthAndLookup(ctxIn context.Context) error {
455398 api .Infof ("Connected to Tag Enum Lookup API server at %s" , address )
456399 api .tagLookupClient = clientLookup
457400
458- return api .getSynthInfo (ctx )
401+ deviceLookup := devicepb .NewDeviceServiceClient (conn )
402+ api .Infof ("Connected to Device Lookup API server at %s" , address )
403+ api .deviceClient = deviceLookup
404+
405+ if api .loadInterfaces {
406+ interfaceLookup := interfacepb .NewInterfaceServiceClient (conn )
407+ api .Infof ("Connected to Interface Lookup API server at %s" , address )
408+ api .interfaceClient = interfaceLookup
409+ }
410+
411+ return api .getSynthAndDeviceInfo (ctx )
459412}
460413
461- func (api * KentikApi ) getSynthInfo (ctx context.Context ) error {
414+ func (api * KentikApi ) getSynthAndDeviceInfo (ctx context.Context ) error {
462415 api .mux .Lock () // Guard this function totally.
463416 defer api .mux .Unlock ()
464417 api .lastSynth = time .Now ()
@@ -512,6 +465,119 @@ func (api *KentikApi) getSynthInfo(ctx context.Context) error {
512465 api .synTests = synTests
513466 api .Infof ("Loaded %d Kentik Tests and %d Agents Total via API" , len (api .synTests ), len (api .synAgents ))
514467
468+ return api .getDeviceInfoNew (ctx )
469+ }
470+
471+ func (api * KentikApi ) getDeviceInfoNew (ctx context.Context ) error {
472+
473+ stime := time .Now ()
474+ resDev := map [kt.Cid ]kt.Devices {}
475+ num := 0
476+ deviceIds := []string {}
477+
478+ for _ , info := range api .config .KentikCreds {
479+ md := metadata .New (map [string ]string {
480+ "X-CH-Auth-Email" : info .APIEmail ,
481+ "X-CH-Auth-API-Token" : info .APIToken ,
482+ })
483+ ctxo := metadata .NewOutgoingContext (ctx , md )
484+
485+ lt := & devicepb.ListDevicesRequest {
486+ Query : & devicepb.DeviceQuery {NoCustomColumns : false },
487+ }
488+ r , err := api .deviceClient .ListDevices (ctxo , lt )
489+ if err != nil {
490+ if status .Code (err ) == codes .Unimplemented {
491+ api .Warnf ("Device ListDevices endpoint not implemented (deprecated API); skipping." )
492+ return nil
493+ }
494+ return err
495+ }
496+
497+ for _ , device := range r .GetDevices () {
498+ dev , err := kt .MapDeviceDetailedToDevice (device )
499+ if err != nil {
500+ continue
501+ }
502+ if _ , ok := resDev [dev .CompanyID ]; ! ok {
503+ resDev [dev .CompanyID ] = map [kt.DeviceID ]* kt.Device {}
504+ }
505+ deviceIds = append (deviceIds , device .Id )
506+ resDev [dev.CompanyID ][dev.ID ] = dev
507+ num ++
508+ }
509+
510+ api .Infof ("Loaded %d Kentik Devices via GRPC for %s" , len (r .GetDevices ()), info .APIEmail )
511+ }
512+
513+ api .devices = resDev
514+
515+ // Now pull in site info for these devices.
516+ err := api .getSites (ctx )
517+ if err != nil {
518+ return err
519+ }
520+
521+ // If we are loading interfaces, pull in interface data now also.
522+ if api .loadInterfaces {
523+ const batchSize = 100
524+ for i := 0 ; i < len (deviceIds ); i += batchSize {
525+ end := min (i + batchSize , len (deviceIds ))
526+ err := api .getInterfaces (ctx , deviceIds [i :end ])
527+ if err != nil {
528+ return err
529+ }
530+ }
531+ }
532+
533+ api .setTime = time .Now ()
534+ api .Infof ("Loaded %d Kentik Devices total via GRPC in %v" , num , api .setTime .Sub (stime ))
535+ return nil
536+ }
537+
538+ func (api * KentikApi ) getInterfaces (ctx context.Context , deviceIds []string ) error {
539+ for _ , info := range api .config .KentikCreds {
540+ api .Infof ("Loading %d device interfaces for %s" , len (deviceIds ), info .APIEmail )
541+ md := metadata .New (map [string ]string {
542+ "X-CH-Auth-Email" : info .APIEmail ,
543+ "X-CH-Auth-API-Token" : info .APIToken ,
544+ })
545+ ctxo := metadata .NewOutgoingContext (ctx , md )
546+
547+ lt := & interfacepb.ListInterfaceRequest {
548+ Filters : & interfacepb.InterfaceFilter {DeviceIds : deviceIds , Text : kt .LookupEnvString (KT_INTERFACE_LOOKUP_TEXT_FILTER , "" )},
549+ }
550+ r , err := api .interfaceClient .ListInterface (ctxo , lt )
551+ if err != nil {
552+ if status .Code (err ) == codes .Unimplemented {
553+ api .Warnf ("Interface ListInterface endpoint not implemented (deprecated API); skipping." )
554+ return nil
555+ }
556+ return err
557+ }
558+
559+ deviceIndex := make (map [kt.DeviceID ]* kt.Device )
560+ for _ , clist := range api .devices {
561+ for did , d := range clist {
562+ deviceIndex [did ] = d
563+ }
564+ }
565+
566+ found := 0
567+ for _ , intf := range r .GetInterfaces () {
568+ deviceID , err := strconv .ParseInt (intf .GetDeviceId (), 10 , 64 )
569+ if err != nil {
570+ continue
571+ }
572+ if dd , ok := deviceIndex [kt .DeviceID (deviceID )]; ok {
573+ dd .AddInterface (intf )
574+ found ++
575+ }
576+ }
577+
578+ api .Infof ("Loaded %d Kentik Interfaces via GRPC for %s" , found , info .APIEmail )
579+ }
580+
515581 return nil
516582}
517583
0 commit comments