gddo-server: add server.configPoller

A config poller is added to the server, which polls for config data
based on the setting of GDDO_CONFIG_LOCATION.

Change-Id: Ib4330c3614a17e32930a09071043e5f2b50e6bf3
Reviewed-on: https://go-review.googlesource.com/c/gddo/+/285932
Trust: Julie Qiu <julie@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/gddo-server/poller.go b/gddo-server/poller.go
new file mode 100644
index 0000000..c209917
--- /dev/null
+++ b/gddo-server/poller.go
@@ -0,0 +1,45 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"time"
+
+	"github.com/golang/gddo/gddo-server/dynconfig"
+	"github.com/golang/gddo/gddo-server/poller"
+	"github.com/golang/gddo/log"
+)
+
+func newConfigPoller(ctx context.Context, pollEvery time.Duration) (_ *poller.Poller, err error) {
+	location := os.Getenv("GDDO_CONFIG_LOCATION")
+	if location == "" {
+		return nil, fmt.Errorf("GDDO_CONFIG_LOCATION is not set")
+	}
+	log.Info(ctx, fmt.Sprintf("GDDO_CONFIG_LOCATION: %q", location))
+
+	log.Info(ctx, fmt.Sprintf("using dynamic config from %s for experiments", location))
+	getter := func(ctx context.Context) (*dynconfig.DynamicConfig, error) {
+		return dynconfig.Read(ctx, location)
+	}
+	initial, err := getter(ctx)
+	// If we can't load the initial state, then fail.
+	if err != nil {
+		return nil, err
+	}
+	p := poller.New(
+		initial,
+		func(ctx context.Context) (interface{}, error) {
+			return getter(ctx)
+		},
+		func(err error) {
+			// Log the error.
+			log.Error(ctx, err.Error())
+		})
+	p.Start(ctx, pollEvery)
+	return p, nil
+}
diff --git a/gddo-server/server.go b/gddo-server/server.go
index 07ebf1f..fdf5ce4 100644
--- a/gddo-server/server.go
+++ b/gddo-server/server.go
@@ -33,6 +33,7 @@
 	"cloud.google.com/go/trace"
 	"github.com/golang/gddo/database"
 	"github.com/golang/gddo/doc"
+	"github.com/golang/gddo/gddo-server/poller"
 	"github.com/golang/gddo/gosrc"
 	"github.com/golang/gddo/httputil"
 	"github.com/golang/gddo/internal/health"
@@ -861,13 +862,14 @@
 }
 
 type server struct {
-	v           *viper.Viper
-	db          *database.Database
-	httpClient  *http.Client
-	gceLogger   *GCELogger
-	templates   templateMap
-	traceClient *trace.Client
-	crawlTopic  *pubsub.Topic
+	v            *viper.Viper
+	db           *database.Database
+	httpClient   *http.Client
+	gceLogger    *GCELogger
+	templates    templateMap
+	traceClient  *trace.Client
+	crawlTopic   *pubsub.Topic
+	configPoller *poller.Poller
 
 	statusPNG http.Handler
 	statusSVG http.Handler
@@ -884,8 +886,12 @@
 		httpClient:     newHTTPClient(v),
 		importGraphSem: make(chan struct{}, 10),
 	}
+	cfg, err := newConfigPoller(ctx, 1*time.Millisecond)
+	if err != nil {
+		log.Printf("newConfigPoller: %q", err)
+	}
+	s.configPoller = cfg
 
-	var err error
 	if proj := s.v.GetString(ConfigProject); proj != "" {
 		if s.traceClient, err = trace.NewClient(ctx, proj); err != nil {
 			return nil, err