コード例 #1
0
ファイル: grpc.go プロジェクト: maniksurtani/quotaservice
func (g *GrpcEndpoint) Allow(ctx context.Context, req *pb.AllowRequest) (*pb.AllowResponse, error) {
	rsp := new(pb.AllowResponse)
	if invalid(req) {
		logging.Printf("Invalid request %+v", req)
		rsp.Status = pb.AllowResponse_REJECTED_INVALID_REQUEST
		return rsp, nil
	}

	var tokensRequested int64 = 1
	if req.TokensRequested > 0 {
		tokensRequested = req.TokensRequested
	}

	wait, err := g.qs.Allow(req.Namespace, req.BucketName, tokensRequested, req.MaxWaitMillisOverride, req.MaxWaitTimeOverride)

	if err != nil {
		if qsErr, ok := err.(quotaservice.QuotaServiceError); ok {
			rsp.Status = toPBStatus(qsErr)
		} else {
			logging.Printf("Caught error %v", err)
			rsp.Status = pb.AllowResponse_REJECTED_SERVER_ERROR
		}
	} else {
		rsp.Status = pb.AllowResponse_OK
		rsp.TokensGranted = req.TokensRequested
		rsp.WaitMillis = wait.Nanoseconds() / int64(time.Millisecond)
	}

	return rsp, nil
}
コード例 #2
0
ファイル: ui.go プロジェクト: maniksurtani/quotaservice
func (h *uiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if h.development {
		if err := h.loadTemplates(); err != nil {
			logging.Printf("Caught error %v reloading templates", err)
		}
	}

	tpl := strings.TrimPrefix(r.URL.Path, "/admin/")

	if tpl == "" {
		tpl = "index.html"
	}

	if h.templates.Lookup(tpl) == nil {
		http.NotFound(w, r)
		return
	}

	err := h.templates.ExecuteTemplate(w, tpl, h.a.Configs())

	if err != nil {
		logging.Printf("Caught error %v serving URL %v", err, r.URL.Path)
		http.Error(w, "500 internal server error", http.StatusInternalServerError)
	}
}
コード例 #3
0
ファイル: bucket.go プロジェクト: maniksurtani/quotaservice
func (bf *bucketFactory) connectToRedisLocked() {
	// Set up connection to Redis
	bf.client = redis.NewClient(bf.redisOpts)
	redisResults := bf.client.Time().Val()
	if len(redisResults) == 0 {
		logging.Printf("Cannot connect to Redis. TIME returned %v", redisResults)
	} else {
		t := time.Unix(toInt64(redisResults[0], 0), 0)
		logging.Printf("Connection established. Time on Redis server: %v", t)
	}
	bf.scriptSHA = loadScript(bf.client)
}
コード例 #4
0
ファイル: redis.go プロジェクト: maniksurtani/quotaservice
// HandleEvent is implemented for stats.Listener
// HandleEvent consumes dynamic bucket events (see events.Event)
func (l *redisListener) HandleEvent(event events.Event) {
	if !event.Dynamic() {
		return
	}

	var key string
	var numTokens int64 = 1

	switch event.EventType() {
	case events.EVENT_BUCKET_MISS:
		key = "misses"
	case events.EVENT_TOKENS_SERVED:
		numTokens = event.NumTokens()
		key = "hits"
	default:
		return
	}

	namespace := statsNamespace(key, event.Namespace())
	bucket := event.BucketName()

	var incr *redis.FloatCmd
	_, err := l.client.Pipelined(func(pipe *redis.Pipeline) error {
		incr = pipe.ZIncrBy(namespace, float64(numTokens), bucket)
		pipe.ExpireAt(namespace, nearestHour())
		return nil
	})

	if err != nil || incr.Err() != nil {
		logging.Printf("RedisStatsListener.HandleEvent error (%s, %s, %d) %v, %v",
			namespace, bucket, numTokens, err, incr.Err())
	}
}
コード例 #5
0
ファイル: admin.go プロジェクト: maniksurtani/quotaservice
func (r *ResponseWrapper) log() {
	timeFormatted := r.time.Format("02/Jan/2006 03:04:05")
	requestLine := fmt.Sprintf("%s %s %s", r.method, r.uri, r.protocol)
	logging.Printf(LogPattern,
		r.ip, timeFormatted, requestLine, r.status, r.responseBytes,
		r.elapsedTime.Seconds())
}
コード例 #6
0
ファイル: grpc.go プロジェクト: maniksurtani/quotaservice
func (g *GrpcEndpoint) Start() {
	lis, err := net.Listen("tcp", g.hostport)
	if err != nil {
		logging.Fatalf("Cannot start server on port %v. Error %v", g.hostport, err)
		panic(fmt.Sprintf("Cannot start server on port %v. Error %v", g.hostport, err))
	}

	grpclog.SetLogger(logging.CurrentLogger())
	g.grpcServer = grpc.NewServer()
	// Each service should be registered
	pb.RegisterQuotaServiceServer(g.grpcServer, g)
	go g.grpcServer.Serve(lis)
	g.currentStatus = lifecycle.Started
	logging.Printf("Starting server on %v", g.hostport)
	logging.Printf("Server status: %v", g.currentStatus)
}
コード例 #7
0
ファイル: bucket.go プロジェクト: maniksurtani/quotaservice
func (b *redisBucket) Take(requested int64, maxWaitTime time.Duration) (time.Duration, bool) {
	currentTimeNanos := strconv.FormatInt(time.Now().UnixNano(), 10)
	args := []string{currentTimeNanos, b.nanosBetweenTokens, b.maxTokensToAccumulate,
		strconv.FormatInt(requested, 10), strconv.FormatInt(maxWaitTime.Nanoseconds(), 10),
		b.maxIdleTimeMillis, b.maxDebtNanos}

	keepTrying := true
	var waitTime time.Duration
	for attempt := 0; keepTrying && attempt < b.factory.connectionRetries; attempt++ {
		client := b.factory.client
		res := client.EvalSha(b.factory.scriptSHA, b.redisKeys, args)
		switch waitTimeNanos := res.Val().(type) {
		case int64:
			waitTime = time.Nanosecond * time.Duration(waitTimeNanos)
			keepTrying = false
		default:
			// Always close connections on errors to prevent results leaking.
			if err := b.factory.client.Close(); err != nil {
				logging.Printf("Received error on redis client close: %+v", err)
			}

			if res.Err() != nil && res.Err().Error() == "redis: client is closed" {
				b.factory.reconnectToRedis(client)
			} else {
				logging.Printf("Unknown response '%v' of type %T. Full result %+v",
					waitTimeNanos, waitTimeNanos, res)
				b.factory.reconnectToRedis(client)
			}
		}
	}

	if keepTrying {
		panic(fmt.Sprintf("Couldn't reconnect to Redis, even after %v attempts",
			b.factory.connectionRetries))
	}

	if waitTime < 0 {
		// Timed out
		return 0, false
	}

	return waitTime, true
}
コード例 #8
0
ファイル: json.go プロジェクト: maniksurtani/quotaservice
func writeJSONError(w http.ResponseWriter, err *HttpError) {
	response := make(map[string]string)
	response["error"] = http.StatusText(err.status)
	response["description"] = err.message

	logging.Printf("Response error: %+v", response)

	w.WriteHeader(err.status)
	writeJSON(w, response)
}
コード例 #9
0
ファイル: bucket.go プロジェクト: maniksurtani/quotaservice
// loadScript loads the LUA script into Redis. The LUA script contains the token bucket algorithm
// which is executed atomically in Redis. Once the script is loaded, it is invoked using its SHA.
func loadScript(c *redis.Client) (sha string) {
	lua := `
	local tokensNextAvailableNanos = tonumber(redis.call("GET", KEYS[1]))
	if not tokensNextAvailableNanos then
		tokensNextAvailableNanos = 0
	end

	local maxTokensToAccumulate = tonumber(ARGV[3])

	local accumulatedTokens = redis.call("GET", KEYS[2])
	if not accumulatedTokens then
		accumulatedTokens = maxTokensToAccumulate
	end

	local currentTimeNanos = tonumber(ARGV[1])
	local nanosBetweenTokens = tonumber(ARGV[2])
	local requested = tonumber(ARGV[4])
	local maxWaitTime = tonumber(ARGV[5])
	local lifespan = tonumber(ARGV[6])
	local maxDebtNanos = tonumber(ARGV[7])
	local freshTokens = 0

	if currentTimeNanos > tokensNextAvailableNanos then
		freshTokens = math.floor((currentTimeNanos - tokensNextAvailableNanos) / nanosBetweenTokens)
		accumulatedTokens = math.min(maxTokensToAccumulate, accumulatedTokens + freshTokens)
		tokensNextAvailableNanos = currentTimeNanos
	end

	local waitTime = tokensNextAvailableNanos - currentTimeNanos
	local accumulatedTokensUsed = math.min(accumulatedTokens, requested)
	local tokensToWaitFor = requested - accumulatedTokensUsed
	local futureWaitNanos = tokensToWaitFor * nanosBetweenTokens

	tokensNextAvailableNanos = tokensNextAvailableNanos + futureWaitNanos
	accumulatedTokens = accumulatedTokens - accumulatedTokensUsed

	if (tokensNextAvailableNanos - currentTimeNanos > maxDebtNanos) or (waitTime > 0 and waitTime > maxWaitTime) then
    	waitTime = -1
	else
		if lifespan > 0 then
			redis.call("SET", KEYS[1], tokensNextAvailableNanos, "PX", lifespan)
			redis.call("SET", KEYS[2], math.floor(accumulatedTokens), "PX", lifespan)
		else
			redis.call("SET", KEYS[1], tokensNextAvailableNanos)
			redis.call("SET", KEYS[2], math.floor(accumulatedTokens))
		end
	end

	return waitTime
	`
	s := c.ScriptLoad(lua)
	sha = s.Val()
	logging.Printf("Loaded LUA script into Redis; script SHA %v", sha)
	return
}
コード例 #10
0
func (z *ZkConfigPersister) currentConfigEventListener() (<-chan zk.Event, error) {
	config, _, ch, err := z.conn.GetW(z.path)

	if err != nil {
		logging.Printf("Received error from zookeeper when fetching %s: %+v", z.path, err)
		return nil, err
	}

	children, _, err := z.conn.Children(z.path)

	if err != nil {
		logging.Printf("Received error from zookeeper when fetching children of %s: %+v", z.path, err)
		return nil, err
	}

	z.Lock()
	defer z.Unlock()

	z.configs = make(map[string][]byte)

	for _, child := range children {
		path := fmt.Sprintf("%s/%s", z.path, child)
		data, _, err := z.conn.Get(path)

		if err != nil {
			logging.Printf("Received error from zookeeper when fetching %s: %+v", path, err)
		} else {
			z.configs[child] = data
		}
	}

	z.config = string(config)

	select {
	case z.watcher <- struct{}{}:
		// Notified
	default:
		// Doesn't matter; another notification is pending.
	}

	return ch, nil
}
コード例 #11
0
ファイル: bucket.go プロジェクト: maniksurtani/quotaservice
func toInt64(s interface{}, defaultValue int64) int64 {
	if s != nil {
		v, err := strconv.ParseInt(s.(string), 10, 64)
		if err != nil {
			logging.Printf("Cannot convert '%v' to int64", s)
			return defaultValue
		}
		return v
	}
	return defaultValue
}
コード例 #12
0
ファイル: bucket.go プロジェクト: maniksurtani/quotaservice
// waitTimeLoop is the single event loop that claims tokens on a given bucket.
func (b *tokenBucket) waitTimeLoop() {
	for {
		select {
		case req := <-b.waitTimer:
			req.response <- b.calcWaitTime(req.requested, req.maxWaitTimeNanos)
		case <-b.closer:
			logging.Printf("Garbage collecting bucket %v", b.fullName)
			// TODO(manik) properly notify goroutines who are currently trying to write to waitTimer
			return
		}
	}
}
コード例 #13
0
ファイル: redis.go プロジェクト: maniksurtani/quotaservice
// Get is implemented for stats.Listener
// Get returns the hits and misses for a bucket in the specified namespace
// within the current bucketed hour
func (l *redisListener) Get(namespace, bucket string) *BucketScores {
	scores := &BucketScores{0, 0}

	value, err := l.client.ZScore(statsNamespace("misses", namespace), bucket).Result()

	if err != nil && err.Error() != "redis: nil" {
		logging.Printf("RedisStatsListener.Get error (%s, %s) %v", namespace, bucket, err)
	} else {
		scores.Misses = int64(value)
	}

	value, err = l.client.ZScore(statsNamespace("hits", namespace), bucket).Result()

	if err != nil && err.Error() != "redis: nil" {
		logging.Printf("RedisStatsListener.Get error (%s, %s) %v", namespace, bucket, err)
	} else {
		scores.Hits = int64(value)
	}

	return scores
}
コード例 #14
0
ファイル: json.go プロジェクト: maniksurtani/quotaservice
func writeJSON(w http.ResponseWriter, object interface{}) {
	b, e := json.Marshal(object)

	if e != nil {
		writeJSONError(w, &HttpError{e.Error(), http.StatusBadRequest})
		return
	}

	_, e = w.Write(b)

	if e != nil {
		logging.Printf("Error writing JSON! %+v", e)
	}
}
コード例 #15
0
ファイル: redis.go プロジェクト: maniksurtani/quotaservice
func (l *redisListener) redisTopList(key string) []*BucketScore {
	results, err := l.client.ZRevRangeWithScores(key, 0, 10).Result()

	if err != nil && err.Error() != "redis: nil" {
		logging.Printf("RedisStatsListener.TopList error (%s) %v", key, err)
		return emptyArr
	}

	arr := make([]*BucketScore, len(results))

	for i, item := range results {
		arr[i] = &BucketScore{item.Member.(string), int64(item.Score)}
	}

	return arr
}
コード例 #16
0
ファイル: main.go プロジェクト: maniksurtani/quotaservice
func main() {
	cfg := config.NewDefaultServiceConfig()
	ns := config.NewDefaultNamespaceConfig("test.namespace")
	ns.DynamicBucketTemplate = config.NewDefaultBucketConfig(config.DynamicBucketTemplateName)
	ns.DynamicBucketTemplate.Size = 100000000000
	ns.DynamicBucketTemplate.FillRate = 100000000
	b := config.NewDefaultBucketConfig("xyz")
	config.AddBucket(ns, b)
	config.AddNamespace(cfg, ns)

	ns = config.NewDefaultNamespaceConfig("test.namespace2")
	ns.DefaultBucket = config.NewDefaultBucketConfig(config.DefaultBucketName)
	b = config.NewDefaultBucketConfig("xyz")
	config.AddBucket(ns, b)
	config.AddNamespace(cfg, ns)

	server := quotaservice.New(memory.NewBucketFactory(),
		config.NewMemoryConfig(cfg),
		grpc.New(GRPC_SERVER))
	server.SetStatsListener(stats.NewMemoryStatsListener())
	server.Start()

	// Serve Admin Console
	logging.Printf("Starting admin server on %v\n", ADMIN_SERVER)
	sm := http.NewServeMux()
	server.ServeAdminConsole(sm, "admin/public", true)
	go func() {
		http.ListenAndServe(ADMIN_SERVER, sm)
	}()

	// Block until SIGTERM, SIGKILL or SIGINT
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGINT)

	var shutdown sync.WaitGroup
	shutdown.Add(1)

	go func() {
		<-sigs
		shutdown.Done()
	}()

	shutdown.Wait()
	server.Stop()
}
コード例 #17
0
ファイル: bucket.go プロジェクト: maniksurtani/quotaservice
// createNewNamedBucket creates a new, named bucket. May return nil if the named bucket is dynamic,
// and the namespace has already reached its maxDynamicBuckets setting.
func (bc *bucketContainer) createNewNamedBucket(namespace, bucketName string, ns *namespace) *expirableBucket {
	bCfg := ns.cfg.Buckets[bucketName]
	dyn := false
	if bCfg == nil {
		// Dynamic.
		numDynamicBuckets := bc.countDynamicBuckets(namespace)
		if numDynamicBuckets >= ns.cfg.MaxDynamicBuckets && ns.cfg.MaxDynamicBuckets > 0 {
			logging.Printf("Bucket %v:%v numDynamicBuckets=%v maxDynamicBuckets=%v. Not creating more dynamic buckets.",
				namespace, bucketName, numDynamicBuckets, ns.cfg.MaxDynamicBuckets)
			return nil
		}

		dyn = true
		bCfg = ns.cfg.DynamicBucketTemplate
	}

	return bc.createNewNamedBucketFromCfg(namespace, bucketName, ns, bCfg, dyn)
}
コード例 #18
0
func (z *ZkConfigPersister) waitForEvents(watch *ZkWatch) {
	defer z.wg.Done()

	for {
		select {
		case event := <-watch.channel:
			if event.Err != nil {
				logging.Print("Received error from zookeeper", event)
			}
		case <-watch.stopper:
			return
		}

		channel, err := watch.listener()

		if err != nil {
			logging.Printf("Received error from zookeeper executing listener: %+v", err)
			continue
		}

		watch.channel = channel
	}
}
コード例 #19
0
ファイル: admin.go プロジェクト: maniksurtani/quotaservice
// ServeAdminConsole serves up an admin console for an Administrable using Go's built-in HTTP server
// library. `assetsDirectory` contains HTML templates and other UI assets. If empty, no UI will be
// served, and only REST endpoints under `/api/` will be served.
func ServeAdminConsole(a Administrable, mux *http.ServeMux, assetsDirectory string, development bool) {
	if assetsDirectory != "" {
		msg := "Serving assets from %s"

		if development {
			msg += " (in development mode)"
		}

		logging.Printf(msg, assetsDirectory)

		mux.Handle("/", loggingHandler(http.RedirectHandler("/admin/", 301)))
		mux.Handle("/admin/", loggingHandler(NewUIHandler(a, assetsDirectory, development)))
		mux.Handle("/js/", loggingHandler(http.FileServer(http.Dir(assetsDirectory))))
		mux.Handle("/favicon.ico", http.NotFoundHandler())
	} else {
		logging.Print("Not serving admin web UI.")
		mux.Handle("/", loggingHandler(http.NotFoundHandler()))
	}

	bucketsHandler := NewBucketsAPIHandler(a)
	namespacesHandler := NewNamespacesAPIHandler(a)

	apiHandler := loggingHandler(jsonResponseHandler(apiRequestHandler(
		namespacesHandler, bucketsHandler)))

	mux.Handle("/api", apiHandler)
	mux.Handle("/api/", apiHandler)

	statsHandler := loggingHandler(jsonResponseHandler(NewStatsAPIHandler(a)))
	mux.Handle("/api/stats", statsHandler)
	mux.Handle("/api/stats/", statsHandler)

	configsHandler := loggingHandler(jsonResponseHandler(NewConfigsAPIHandler(a)))
	mux.Handle("/api/configs", configsHandler)
	mux.Handle("/api/configs/", configsHandler)
}
コード例 #20
0
// Tries to create the configuration path, if it doesn't exist
// It tries multiple times in case there's a race with another quotaservice node coming up
func createPath(conn *zk.Conn, path string) (err error) {
	for i := 0; i < createRetries; i++ {
		exists, _, err := conn.Exists(path)

		if exists && err == nil {
			return nil
		}

		_, err = conn.Create(path, []byte{}, 0, zk.WorldACL(zk.PermAll))

		if err == nil {
			return nil
		}

		logging.Printf("Could not create zk path, sleeping for 100ms")
		time.Sleep(100 * time.Millisecond)
	}

	if err == nil {
		err = errors.New("could not create and get path " + path)
	}

	return err
}
コード例 #21
0
ファイル: json.go プロジェクト: maniksurtani/quotaservice
func writeJSONOk(w http.ResponseWriter) {
	if _, e := w.Write(emptyJsonResponse); e != nil {
		logging.Printf("Error writing JSON! %+v", e)
	}
}