github.com/go-graphite/carbonapi@v0.17.0/zipper/zipper.go (about) 1 package zipper 2 3 import ( 4 "context" 5 _ "net/http/pprof" 6 "time" 7 8 "github.com/ansel1/merry" 9 protov3 "github.com/go-graphite/protocol/carbonapi_v3_pb" 10 "go.uber.org/zap" 11 12 utilctx "github.com/go-graphite/carbonapi/util/ctx" 13 "github.com/go-graphite/carbonapi/zipper/broadcast" 14 "github.com/go-graphite/carbonapi/zipper/config" 15 "github.com/go-graphite/carbonapi/zipper/helper" 16 "github.com/go-graphite/carbonapi/zipper/metadata" 17 "github.com/go-graphite/carbonapi/zipper/types" 18 19 _ "github.com/go-graphite/carbonapi/zipper/protocols/auto" 20 _ "github.com/go-graphite/carbonapi/zipper/protocols/graphite" 21 _ "github.com/go-graphite/carbonapi/zipper/protocols/irondb" 22 _ "github.com/go-graphite/carbonapi/zipper/protocols/prometheus" 23 _ "github.com/go-graphite/carbonapi/zipper/protocols/v2" 24 _ "github.com/go-graphite/carbonapi/zipper/protocols/v3" 25 _ "github.com/go-graphite/carbonapi/zipper/protocols/victoriametrics" 26 ) 27 28 // Zipper provides interface to Zipper-related functions 29 type Zipper struct { 30 probeTicker *time.Ticker 31 ProbeQuit chan struct{} 32 ProbeForce chan int 33 34 timeout time.Duration 35 timeoutConnect time.Duration 36 keepAliveInterval time.Duration 37 38 // Will broadcast to all servers there 39 backend types.BackendServer 40 concurrencyLimitPerServer int 41 42 ScaleToCommonStep bool 43 44 sendStats func(*types.Stats) 45 46 logger *zap.Logger 47 } 48 49 func createBackendsV2(logger *zap.Logger, backends types.BackendsV2, expireDelaySec int32, tldCacheDisabled, requireSuccessAll bool) ([]types.BackendServer, merry.Error) { 50 backendServers := make([]types.BackendServer, 0) 51 var e merry.Error 52 timeouts := backends.Timeouts 53 for _, backend := range backends.Backends { 54 concurrencyLimit := backends.ConcurrencyLimitPerServer 55 tries := backends.MaxTries 56 maxIdleConnsPerHost := backends.MaxIdleConnsPerHost 57 keepAliveInterval := backends.KeepAliveInterval 58 maxBatchSize := backends.MaxBatchSize 59 60 if backend.Timeouts == nil { 61 backend.Timeouts = &timeouts 62 } 63 if backend.ConcurrencyLimit == nil { 64 backend.ConcurrencyLimit = &concurrencyLimit 65 } 66 if backend.MaxTries == nil { 67 backend.MaxTries = &tries 68 } 69 if backend.MaxBatchSize == nil { 70 backend.MaxBatchSize = maxBatchSize 71 } 72 if backend.MaxIdleConnsPerHost == nil { 73 backend.MaxIdleConnsPerHost = &maxIdleConnsPerHost 74 } 75 if backend.KeepAliveInterval == nil { 76 backend.KeepAliveInterval = &keepAliveInterval 77 } 78 79 var backendServer types.BackendServer 80 logger.Debug("creating lb group", 81 zap.String("name", backend.GroupName), 82 zap.Strings("servers", backend.Servers), 83 zap.Any("type", backend.LBMethod), 84 ) 85 86 metadata.Metadata.RLock() 87 backendInit, ok := metadata.Metadata.ProtocolInits[backend.Protocol] 88 metadata.Metadata.RUnlock() 89 if !ok { 90 var protocols []string 91 metadata.Metadata.RLock() 92 for p := range metadata.Metadata.SupportedProtocols { 93 protocols = append(protocols, p) 94 } 95 metadata.Metadata.RUnlock() 96 logger.Error("unknown backend protocol", 97 zap.Any("backend", backend), 98 zap.String("requested_protocol", backend.Protocol), 99 zap.Strings("supported_backends", protocols), 100 ) 101 return nil, merry.Errorf("unknown backend protocol '%v'", backend.Protocol) 102 } 103 104 var lbMethod types.LBMethod 105 err := lbMethod.FromString(backend.LBMethod) 106 if err != nil { 107 logger.Fatal("failed to parse lbMethod", 108 zap.String("lbMethod", backend.LBMethod), 109 zap.Error(err), 110 ) 111 } 112 if lbMethod == types.RoundRobinLB { 113 backendServer, e = backendInit(logger, backend, tldCacheDisabled, requireSuccessAll) 114 if e != nil { 115 return nil, e 116 } 117 } else { 118 config := backend 119 120 backendServers := make([]types.BackendServer, 0, len(backend.Servers)) 121 for _, server := range backend.Servers { 122 config.Servers = []string{server} 123 config.GroupName = server 124 backendServer, e = backendInit(logger, config, tldCacheDisabled, requireSuccessAll) 125 if e != nil { 126 return nil, e 127 } 128 backendServers = append(backendServers, backendServer) 129 } 130 131 backendServer, err = broadcast.NewBroadcastGroup(logger, backend.GroupName, backend.DoMultipleRequestsIfSplit, backendServers, 132 expireDelaySec, *backend.ConcurrencyLimit, *backend.MaxBatchSize, timeouts, tldCacheDisabled, requireSuccessAll, 133 ) 134 if err != nil { 135 return nil, merry.Wrap(err) 136 } 137 } 138 backendServers = append(backendServers, backendServer) 139 } 140 return backendServers, nil 141 } 142 143 // NewZipper allows to create new Zipper 144 func NewZipper(sender func(*types.Stats), cfg *config.Config, logger *zap.Logger) (*Zipper, merry.Error) { 145 if !cfg.IsSanitized() { 146 cfg = config.SanitizeConfig(logger, *cfg) 147 } 148 149 backends, err := createBackendsV2(logger, cfg.BackendsV2, int32(cfg.InternalRoutingCache.Seconds()), cfg.TLDCacheDisabled, cfg.RequireSuccessAll) 150 if err != nil { 151 logger.Fatal("errors while initialing zipper store backend", 152 zap.Any("error", err), 153 ) 154 } 155 156 logger.Error("DEBUG ERROR LOGGGGG", zap.Any("cfg", cfg)) 157 broadcastGroup, err := broadcast.NewBroadcastGroup(logger, "root", cfg.DoMultipleRequestsIfSplit, backends, 158 int32(cfg.InternalRoutingCache.Seconds()), cfg.ConcurrencyLimitPerServer, *cfg.MaxBatchSize, cfg.Timeouts, cfg.TLDCacheDisabled, cfg.RequireSuccessAll, 159 ) 160 if err != nil { 161 logger.Fatal("error while initialing zipper store backend", 162 zap.Any("error", err), 163 ) 164 } 165 166 z := &Zipper{ 167 ProbeQuit: make(chan struct{}), 168 ProbeForce: make(chan int), 169 170 ScaleToCommonStep: cfg.ScaleToCommonStep, 171 sendStats: sender, 172 173 backend: broadcastGroup, 174 concurrencyLimitPerServer: cfg.ConcurrencyLimitPerServer, 175 keepAliveInterval: cfg.KeepAliveInterval, 176 timeout: cfg.Timeouts.Render, 177 timeoutConnect: cfg.Timeouts.Connect, 178 logger: logger, 179 } 180 181 logger.Debug("zipper config", 182 zap.Any("config", cfg), 183 ) 184 185 if !cfg.TLDCacheDisabled { 186 z.probeTicker = time.NewTicker(cfg.InternalRoutingCache) 187 188 go z.probeTlds() 189 190 z.ProbeForce <- 1 191 } 192 return z, nil 193 } 194 195 func (z *Zipper) doProbe(logger *zap.Logger) { 196 ctx := context.Background() 197 198 _, err := z.backend.ProbeTLDs(ctx) 199 if err != nil { 200 logger.Error("failed to probe tlds", 201 zap.String("errors", err.Cause().Error()), 202 ) 203 if ce := logger.Check(zap.DebugLevel, "failed to probe tlds (verbose)"); ce != nil { 204 ce.Write( 205 zap.Any("errorVerbose", err), 206 ) 207 } 208 } 209 } 210 211 func (z *Zipper) probeTlds() { 212 logger := z.logger.With(zap.String("type", "probe")) 213 for { 214 select { 215 case <-z.probeTicker.C: 216 z.doProbe(logger) 217 case <-z.ProbeForce: 218 z.doProbe(logger) 219 case <-z.ProbeQuit: 220 z.probeTicker.Stop() 221 return 222 } 223 } 224 } 225 226 // GRPC-compatible methods 227 func (z Zipper) FetchProtoV3(ctx context.Context, request *protov3.MultiFetchRequest) (*protov3.MultiFetchResponse, *types.Stats, merry.Error) { 228 logger := z.logger.With(zap.String("function", "FetchProtoV3"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 229 230 res, stats, e := z.backend.Fetch(ctx, request) 231 232 if e != nil { 233 logger.Debug("had errors while fetching result", 234 zap.Any("errors", e), 235 zap.Int("httpCode", merry.HTTPCode(e)), 236 ) 237 } 238 if res == nil || len(res.Metrics) == 0 { 239 logger.Debug("no metrics fetched", 240 zap.Any("errors", e), 241 ) 242 243 err := helper.HttpErrorByCode(e) 244 245 return nil, stats, err 246 } 247 248 return res, stats, merry.WithHTTPCode(e, 200) 249 } 250 251 func (z Zipper) FindProtoV3(ctx context.Context, request *protov3.MultiGlobRequest) (*protov3.MultiGlobResponse, *types.Stats, merry.Error) { 252 logger := z.logger.With(zap.String("function", "FindProtoV3"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 253 254 res, stats, err := z.backend.Find(ctx, request) 255 256 var errs []merry.Error 257 if err != nil { 258 errs = []merry.Error{err} 259 } 260 261 findResponse := &types.ServerFindResponse{ 262 Response: res, 263 Stats: stats, 264 Err: errs, 265 } 266 267 if len(findResponse.Err) > 0 { 268 var e merry.Error 269 if len(findResponse.Err) == 1 { 270 e = helper.HttpErrorByCode(findResponse.Err[0]) 271 } else { 272 e = helper.HttpErrorByCode(findResponse.Err[1].WithCause(findResponse.Err[0])) 273 } 274 logger.Debug("had errors while fetching result", 275 zap.Any("errors", e), 276 ) 277 // TODO(civil): Not Found error cases across all zipper code should be handled in the same way 278 // See FetchProtoV3 for more examples 279 if findResponse.Response != nil && len(findResponse.Response.Metrics) > 0 { 280 return findResponse.Response, findResponse.Stats, merry.WithHTTPCode(e, 200) 281 } 282 return nil, stats, e 283 } 284 285 return findResponse.Response, findResponse.Stats, nil 286 } 287 288 func (z Zipper) InfoProtoV3(ctx context.Context, request *protov3.MultiGlobRequest) (*protov3.ZipperInfoResponse, *types.Stats, merry.Error) { 289 logger := z.logger.With(zap.String("function", "InfoProtoV3"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 290 realRequest := &protov3.MultiMetricsInfoRequest{Names: make([]string, 0, len(request.Metrics))} 291 res, _, err := z.FindProtoV3(ctx, request) 292 if err == nil || merry.Is(err, types.ErrNonFatalErrors) { 293 for _, m := range res.Metrics { 294 for _, match := range m.Matches { 295 if match.IsLeaf { 296 realRequest.Names = append(realRequest.Names, match.Path) 297 } 298 } 299 } 300 } else { 301 realRequest.Names = append(realRequest.Names, request.Metrics...) 302 } 303 304 r, stats, e := z.backend.Info(ctx, realRequest) 305 if e != nil { 306 if merry.Is(e, types.ErrNotFound) { 307 return nil, nil, e 308 } else { 309 logger.Debug("had errors while fetching result", 310 zap.Any("errors", e), 311 ) 312 return nil, stats, e 313 } 314 } 315 316 return r, stats, nil 317 } 318 319 func (z Zipper) ListProtoV3(ctx context.Context) (*protov3.ListMetricsResponse, *types.Stats, merry.Error) { 320 logger := z.logger.With(zap.String("function", "ListProtoV3"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 321 r, stats, e := z.backend.List(ctx) 322 if e != nil { 323 if merry.Is(e, types.ErrNotFound) { 324 return nil, nil, e 325 } else { 326 logger.Debug("had errors while fetching result", 327 zap.Any("errors", e), 328 ) 329 return r, stats, e 330 } 331 } 332 333 return r, stats, e 334 } 335 func (z Zipper) StatsProtoV3(ctx context.Context) (*protov3.MetricDetailsResponse, *types.Stats, merry.Error) { 336 logger := z.logger.With(zap.String("function", "StatsProtoV3"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 337 r, stats, e := z.backend.Stats(ctx) 338 if e != nil { 339 if merry.Is(e, types.ErrNotFound) { 340 return nil, nil, e 341 } else { 342 logger.Debug("had errors while fetching result", 343 zap.Any("errors", e), 344 ) 345 return r, stats, e 346 } 347 } 348 349 return r, stats, nil 350 } 351 352 // Tags 353 354 func (z Zipper) TagNames(ctx context.Context, query string, limit int64) ([]string, merry.Error) { 355 logger := z.logger.With(zap.String("function", "TagNames"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 356 data, err := z.backend.TagNames(ctx, query, limit) 357 if err != nil { 358 logger.Debug("had errors while fetching result", 359 zap.Any("errors", err), 360 ) 361 return data, err 362 } 363 364 return data, nil 365 } 366 367 func (z Zipper) TagValues(ctx context.Context, query string, limit int64) ([]string, merry.Error) { 368 logger := z.logger.With(zap.String("function", "TagValues"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 369 data, err := z.backend.TagValues(ctx, query, limit) 370 if err != nil { 371 logger.Debug("had errors while fetching result", 372 zap.Any("errors", err), 373 ) 374 return data, err 375 } 376 377 return data, nil 378 }