Skip to content

Commit

Permalink
prepare 5.5.1 release (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
eli-darkly authored Dec 20, 2018
1 parent 5348ec9 commit 69fbdcf
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 104 deletions.
4 changes: 2 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ You can configure LDR nodes to persist feature flag settings in Redis, DynamoDB,
localTtl = 30000
```

Note that the relay can only use _one_ of these at a time; for instance, enabling both Redis and DynamoDB is an error.

Also note that the LaunchDarkly SDK clients have their own options for configuring persistent storage. If you are using daemon mode (see below) then the clients need to be using the same storage configuration as the relay. If you are not using daemon mode, then the two configurations are completely independent, e.g. you could have a relay using Redis, but a client using Consul or not using persistent storage at all.

Relay proxy mode
----------------
LDR is typically deployed in relay proxy mode. In this mode, several LDR instances are deployed in a high-availability configuration behind a load balancer. LDR nodes do not need to communicate with each other, and there is no master or cluster. This makes it easy to scale LDR horizontally by deploying more nodes behind the load balancer.
Expand Down Expand Up @@ -347,40 +351,43 @@ In docker, the config file is expected to be found at `/ldr/ld-relay.conf` unles
### Docker environment variables
The docker entrypoint uses environment variables to configured the dockerized LD Relay instance.

Note that environment variables are always strings, so the ones listed as "boolean" or "number" are simply describing how the relay will interpret that string. For boolean settings, a value of either `true` or `1` is considered true while any other value is considered false.

environment variable | type | default | description
---------------------------- |:--------------:|:---------------------------------:| -----------
STREAM_URI | URI | `https://stream.launchdarkly.com` |
BASE_URI | URI | `https://app.launchdarkly.com` |
USE_REDIS | Boolean | `false` | If set to `true` or 1, Redis configuration will be added.
USE_REDIS | Boolean | `false` | If true, Redis configuration will be added.
REDIS_HOST | URI | `redis` | Sets the hostname of the Redis server. If linked to a redis container that sets `REDIS_PORT` to `tcp://172.17.0.2:6379`, `REDIS_HOST` will use this value as the default.
REDIS_PORT | Port | `6379` | Sets the port of the Redis server. If linked to a redis container that sets `REDIS_PORT` to `REDIS_PORT=tcp://172.17.0.2:6379`, `REDIS_PORT` will use this value as the default.
REDIS_TTL | Number | `30000` | Alternate name for CACHE_TTL
USE_DYNAMODB | Boolean | `false` | If set to `true` or 1, DynamoDB configuration will be added. You must also specify a table name with either `DYNAMODB_TABLE` or `LD_TABLE_NAME_*env_name*` as described below.
DYNAMODB_TABLE | String | | DynamoDB table name, if any; if you are using a different table for each environment, leave this blank
USE_DYNAMODB | Boolean | `false` | If true, DynamoDB configuration will be added. You must also specify a table name with either `DYNAMODB_TABLE` or `LD_TABLE_NAME_*env_name*` as described below.
DYNAMODB_TABLE | String | | DynamoDB table name, if any; if you are using a different table for each environment, leave this blank.
CACHE_TTL | Number | `30000` | Sets the local cache TTL in milliseconds if you are using a database.
USE_EVENTS | Number | `false` | If set to `true` or 1, enables event buffering.
USE_EVENTS | Boolean | `false` | Enables event buffering.
EVENTS_HOST | URI | `https://events.launchdarkly.com` | URI of the LaunchDarkly events endpoint.
EVENTS_FLUSH_INTERVAL | Number | `5` | Sets how often events are flushed, in seconds.
EVENTS_SAMPLING_INTERVAL | Number | `0` |
EXIT_ON_ERROR | Boolean | `false` |
EXIT_ON_ERROR | Boolean | `false` | If true, the relay will quit at startup time if it cannot establish a connection to LaunchDarkly.
HEARTBEAT_INTERVAL | Number | `15` |
EVENTS_CAPACITY | Number | `10000` |
LD_ENV_*env_name* | SDK Key | | At least one `LD_ENV_${environment}` variable is recommended. The value should be the SDK key for that specific environment. Multiple environments can be listed.
LD_MOBILE_KEY_*env_name* | Mobile Key | | The value should be the Mobile key for that specific environment. Multiple environments can be listed.
LD_CLIENT_SIDE_ID_*env_name* | Client-side ID | | The value should be the environment ID for that specific environment (this is used by the browser JavaScript SDK). Multiple environments can be listed.
LD_PREFIX_*env_name* | String | | Configures a database key prefix for that specific environment (with Redis, Consul, or DynamoDB). Multiple environments can be listed.
LD_TABLE_NAME_*env_name* | String | | Configures a database table name for that specific environment (with DynamoDB only). Multiple environments can be listed.
USE_DATADOG | Number | `0` | If set to 1, enables metric exports to DataDog.
USE_DATADOG | Boolean | `false` | Enables metric exports to DataDog.
DATADOG_STATS_ADDR | String | `localhost:8125` | URI of the DataDog stats agent.
DATADOG_TRACE_ADDR | String | `localhost:8126` | URI of the DataDog trace agent.
DATADOG_PREFIX | String | | Configure a prefix for DataDog metric names.
DATADOG_TAG_*tag_name* | String | | Configure tags to be associated with DataDog metrics.
USE_STACKDRIVER | Number | `0` | If set to 1, enables metric exports to Stackdriver.
USE_STACKDRIVER | Boolean | `false` | Enables metric exports to Stackdriver.
STACKDRIVER_PROJECT_ID | String | | Stackdriver project id. Required to successfully export metrics to Stackdriver.
STACKDRIVER_PREFIX | String | | Configure a prefix for Stackdriver metric names.
USE_PROMETHEUS | Number | `0` | If set to 1, enables metric exports to Prometheus.
USE_PROMETHEUS | Boolean | `false` | Enables metric exports to Prometheus.
PROMETHEUS_PREFIX | String | | Configure a prefix for Prometheus metric names.
PROMETHEUS_PORT | Number | 8031 | The port that ld-relay will listen to `/metrics` on.

### Docker examples
To run a single environment, without Redis:
```
Expand Down
10 changes: 8 additions & 2 deletions client-side.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,17 @@ func init() {
func getEventsImage(w http.ResponseWriter, req *http.Request) {
clientCtx := getClientContext(req)

if clientCtx.getHandlers().eventsHandler == nil {
if clientCtx.getHandlers().eventDispatcher == nil {
w.WriteHeader(http.StatusServiceUnavailable)
w.Write(util.ErrorJsonMsg("Event proxy is not enabled for this environment"))
return
}
handler := clientCtx.getHandlers().eventDispatcher.GetHandler(events.JavaScriptSDKEventsEndpoint)
if handler == nil {
w.WriteHeader(http.StatusServiceUnavailable)
w.Write(util.ErrorJsonMsg("Event proxy for browser clients is not enabled for this environment"))
return
}

d := req.URL.Query().Get("d")
if d != "" {
Expand All @@ -128,7 +134,7 @@ func getEventsImage(w http.ResponseWriter, req *http.Request) {
eventsReq, _ := http.NewRequest("POST", "", bytes.NewBuffer(events))
eventsReq.Header.Add("Content-Type", "application/json")
eventsReq.Header.Add("X-LaunchDarkly-User-Agent", eventsReq.Header.Get("X-LaunchDarkly-User-Agent"))
clientCtx.getHandlers().eventsHandler.ServeHTTP(nullW, eventsReq)
handler(nullW, eventsReq)
}()
}

Expand Down
37 changes: 28 additions & 9 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@ port = 8030
heartbeatIntervalSecs = ${HEARTBEAT_INTERVAL:-15}
" > $CONF_FILE

if [ "$USE_REDIS" = 1 ] || [ "$USE_REDIS" == "true" ]; then
if [ -z "${REDIS_HOST}" ] && [ -z "${REDIS_PORT}" ] && [ -z "${REDIS_URL}" ]; then
CONFIGURED_DATABASE=""

if [ "$USE_REDIS" = "1" ] || [ "$USE_REDIS" = "true" ]; then
if [ -n "$CONFIGURED_DATABASE" ]; then
echo "Please enable at most one database (Redis, DynamoDB, or Consul)"
exit 1
fi
CONFIGURED_DATABASE=redis

if [ -z "${REDIS_HOST}${REDIS_PORT}" ] && [ -z "${REDIS_URL}" ]; then
echo "Choose REDIS_HOST and REDIS_PORT or REDIS_URL"
exit 1
fi
Expand Down Expand Up @@ -50,13 +58,19 @@ port = ${REDIS_PORT:-6379}
elif [ -n "${REDIS_URL+x}" ]; then

echo "
url = "${REDIS_URL}"
url = \"${REDIS_URL}\"
" >> $CONF_FILE

fi
fi

if [ "$USE_DYNAMODB" = 1 ] || [ "$USE_DYNAMODB" == "true" ]; then
if [ "$USE_DYNAMODB" = "1" ] || [ "$USE_DYNAMODB" = "true" ]; then
if [ -n "$CONFIGURED_DATABASE" ]; then
echo "Please enable at most one database (Redis, DynamoDB, or Consul)"
exit 1
fi
CONFIGURED_DATABASE=dynamodb

echo "
[dynamoDB]
enabled = true
Expand All @@ -65,15 +79,20 @@ localTtl = ${CACHE_TTL:-30000}
" >> $CONF_FILE
fi

if [ "$USE_CONSUL" = 1 ] || [ "$USE_CONSUL" == "true" ]; then
if [ "$USE_CONSUL" = "1" ] || [ "$USE_CONSUL" = "true" ]; then
if [ -n "$CONFIGURED_DATABASE" ]; then
echo "Please enable at most one database (Redis, DynamoDB, or Consul)"
exit 1
fi
CONFIGURED_DATABASE=consul
echo "
[consul]
host = \"${CONSUL_HOST:-localhost}\"
localTtl = ${CACHE_TTL:-30000}
" >> $CONF_FILE
fi

if [ "$USE_EVENTS" = 1 ] || [ "$USE_EVENTS" == "true" ]; then
if [ "$USE_EVENTS" = "1" ] || [ "$USE_EVENTS" = "true" ]; then
echo "
[events]
eventsUri = \"${EVENTS_HOST:-https://events.launchdarkly.com}\"
Expand Down Expand Up @@ -108,7 +127,7 @@ done

fi

if [ "$USE_DATADOG" = 1 ] || [ "$USE_DATADOG" == "true" ]; then
if [ "$USE_DATADOG" = "1" ] || [ "$USE_DATADOG" = "true" ]; then
echo "
[datadog]
enabled = true
Expand All @@ -124,7 +143,7 @@ echo "
" >> $CONF_FILE
fi

if [ "$USE_STACKDRIVER" = 1 ] || [ "$USE_STACKDRIVER" == "true" ]; then
if [ "$USE_STACKDRIVER" = "1" ] || [ "$USE_STACKDRIVER" = "true" ]; then
echo "
[stackdriver]
enabled = true
Expand All @@ -133,7 +152,7 @@ prefix = \"${STACKDRIVER_PREFIX}\"
" >> $CONF_FILE
fi

if [ "$USE_PROMETHEUS" = 1 ] || [ "$USE_PROMETHEUS" == "true" ]; then
if [ "$USE_PROMETHEUS" = "1" ] || [ "$USE_PROMETHEUS" = "true" ]; then
echo "
[prometheus]
enabled = true
Expand Down
93 changes: 76 additions & 17 deletions internal/events/event-relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package events

import (
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"strconv"
"strings"
"sync"
"time"

Expand All @@ -25,6 +27,23 @@ type Config struct {
InlineUsers bool
}

// Describes one of the possible endpoints (on both events.launchdarkly.com and the relay) for posting events
type Endpoint interface {
fmt.Stringer
}

type (
serverSDKEventsEndpoint struct{}
mobileSDKEventsEndpoint struct{}
javaScriptSDKEventsEndpoint struct{}
)

var (
ServerSDKEventsEndpoint = &serverSDKEventsEndpoint{}
MobileSDKEventsEndpoint = &mobileSDKEventsEndpoint{}
JavaScriptSDKEventsEndpoint = &javaScriptSDKEventsEndpoint{}
)

type eventVerbatimRelay struct {
config Config
publisher EventPublisher
Expand All @@ -45,19 +64,42 @@ const (
EventSchemaHeader = "X-LaunchDarkly-Event-Schema"
)

// EventRelayHandler is a handler for relaying events to LaunchDarkly for an environment
type EventRelayHandler struct {
config Config
sdkKey string
featureStore ld.FeatureStore
// EventDispatcher relays events to LaunchDarkly for an environment
type EventDispatcher struct {
endpoints map[Endpoint]*eventEndpointDispatcher
}

type eventEndpointDispatcher struct {
config Config
authKey string
remotePath string
verbatimRelay *eventVerbatimRelay
summarizingRelay *eventSummarizingRelay
featureStore ld.FeatureStore
mu sync.Mutex
}

func (e *serverSDKEventsEndpoint) String() string {
return "ServerSDKEventsEndpoint"
}

mu sync.Mutex
func (e *mobileSDKEventsEndpoint) String() string {
return "MobileSDKEventsEndpoint"
}

func (r *EventRelayHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
func (e *javaScriptSDKEventsEndpoint) String() string {
return "JavaScriptSDKEventsEndpoint"
}

func (r *EventDispatcher) GetHandler(endpoint Endpoint) func(w http.ResponseWriter, req *http.Request) {
d := r.endpoints[endpoint]
if d != nil {
return d.dispatchEvents
}
return nil
}

func (r *eventEndpointDispatcher) dispatchEvents(w http.ResponseWriter, req *http.Request) {
body, bodyErr := ioutil.ReadAll(req.Body)

if bodyErr != nil {
Expand Down Expand Up @@ -94,6 +136,7 @@ func (r *EventRelayHandler) ServeHTTP(w http.ResponseWriter, req *http.Request)
if payloadVersion == 0 {
payloadVersion = 1
}
logging.Debug.Printf("Received %d events (v%d) to be proxied to %s", len(evts), payloadVersion, r.remotePath)
if payloadVersion >= SummaryEventsSchemaVersion {
// New-style events that have already gone through summarization - deliver them as-is
r.getVerbatimRelay().enqueue(evts)
Expand All @@ -103,37 +146,53 @@ func (r *EventRelayHandler) ServeHTTP(w http.ResponseWriter, req *http.Request)
}()
}

func (r *EventRelayHandler) getVerbatimRelay() *eventVerbatimRelay {
func (r *eventEndpointDispatcher) getVerbatimRelay() *eventVerbatimRelay {
r.mu.Lock()
defer r.mu.Unlock()
if r.verbatimRelay == nil {
r.verbatimRelay = newEventVerbatimRelay(r.sdkKey, r.config)
r.verbatimRelay = newEventVerbatimRelay(r.authKey, r.config, r.remotePath)
}
return r.verbatimRelay
}

func (r *EventRelayHandler) getSummarizingRelay() *eventSummarizingRelay {
func (r *eventEndpointDispatcher) getSummarizingRelay() *eventSummarizingRelay {
r.mu.Lock()
defer r.mu.Unlock()
if r.summarizingRelay == nil {
r.summarizingRelay = newEventSummarizingRelay(r.sdkKey, r.config, r.featureStore)
r.summarizingRelay = newEventSummarizingRelay(r.authKey, r.config, r.featureStore, r.remotePath)
}
return r.summarizingRelay
}

// NewEventRelayHandler create a handler for relaying events to LaunchDarkly for an environment
func NewEventRelayHandler(sdkKey string, config Config, featureStore ld.FeatureStore) *EventRelayHandler {
return &EventRelayHandler{
sdkKey: sdkKey,
// NewEventDispatcher creates a handler for relaying events to LaunchDarkly for an environment
func NewEventDispatcher(sdkKey string, mobileKey *string, envID *string, config Config, featureStore ld.FeatureStore) *EventDispatcher {
ep := &EventDispatcher{
endpoints: map[Endpoint]*eventEndpointDispatcher{
ServerSDKEventsEndpoint: newEventEndpointDispatcher(sdkKey, config, featureStore, "/bulk"),
},
}
if mobileKey != nil {
ep.endpoints[MobileSDKEventsEndpoint] = newEventEndpointDispatcher(*mobileKey, config, featureStore, "/mobile")
}
if envID != nil {
ep.endpoints[JavaScriptSDKEventsEndpoint] = newEventEndpointDispatcher("", config, featureStore, "/events/bulk/"+*envID)
}
return ep
}

func newEventEndpointDispatcher(authKey string, config Config, featureStore ld.FeatureStore, remotePath string) *eventEndpointDispatcher {
return &eventEndpointDispatcher{
authKey: authKey,
config: config,
featureStore: featureStore,
remotePath: remotePath,
}
}

func newEventVerbatimRelay(sdkKey string, config Config) *eventVerbatimRelay {
func newEventVerbatimRelay(sdkKey string, config Config, remotePath string) *eventVerbatimRelay {
opts := []OptionType{
OptionCapacity(config.Capacity),
OptionUri(config.EventsUri),
OptionEndpointURI(strings.TrimRight(config.EventsUri, "/") + remotePath),
}

if config.FlushIntervalSecs > 0 {
Expand Down
5 changes: 3 additions & 2 deletions internal/events/event-summarizing-relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"strings"
"time"

ld "gopkg.in/launchdarkly/go-client.v4"
Expand All @@ -16,9 +17,9 @@ type eventSummarizingRelay struct {
featureStore ld.FeatureStore
}

func newEventSummarizingRelay(sdkKey string, config Config, featureStore ld.FeatureStore) *eventSummarizingRelay {
func newEventSummarizingRelay(sdkKey string, config Config, featureStore ld.FeatureStore, remotePath string) *eventSummarizingRelay {
ldConfig := ld.DefaultConfig
ldConfig.EventsUri = config.EventsUri
ldConfig.EventsEndpointUri = strings.TrimRight(config.EventsUri, "/") + remotePath
ldConfig.Capacity = config.Capacity
ldConfig.InlineUsersInEvents = config.InlineUsers
ldConfig.FlushInterval = time.Duration(config.FlushIntervalSecs) * time.Second
Expand Down
Loading

0 comments on commit 69fbdcf

Please sign in to comment.