Add rake task to populate Consul from dynamic_settings.yml

Fixes: CNVS-39293

Since we eliminated the pre-population functionality from our Consul
wrapper we needed something to conveniently populate the KV store.

Test Plan:
- Start a Consul server
- Run `bin/rake canvas:seed_consul`
- Verify that values were written to the KV store.

Change-Id: I340011b7d00ed4e3dd2918e3f101f6377fc72d7e
Reviewed-on: https://gerrit.instructure.com/126574
Reviewed-by: Cody Cutrer <cody@instructure.com>
Product-Review: Tyler Pickett <tpickett@instructure.com>
QA-Review: Cody Cutrer <cody@instructure.com>
Tested-by: Jenkins
This commit is contained in:
Tyler Pickett 2017-09-19 10:33:08 -05:00
parent 7e932a4498
commit 099365aec1
10 changed files with 179 additions and 76 deletions

View File

@ -114,7 +114,7 @@ gem 'canvas_statsd', '2.0.4'
gem 'statsd-ruby', '1.4.0', require: false
gem 'aroi', '0.0.5', require: false
gem 'gepub', '0.7.0beta4'
gem 'imperium', '0.1.3', require: false
gem 'imperium', '0.2.1', require: false
gem 'academic_benchmarks', '0.0.10', require: false
gem 'graphql', '1.6.7'

View File

@ -5,33 +5,45 @@
# shaped like the example below, one key for the related set of data,
# and a hash of key/value pairs (no nesting)
development:
address-book:
app-host: "http://address-book.docker"
secret: "opensesame"
canvas:
encryption-secret: "astringthatisactually32byteslong"
signing-secret: "astringthatisactually32byteslong"
ha_cache: |
cache_store: ha_store
servers:
- redis://localhost/2
# keep stale data for up to 1 week in the cache
race_condition_ttl: 604800
# how long it might take to recompute a cache value
# before the lock times out and another process is
# allowed to write it
lock_timeout: 5
# how long before a cache entry is considered stale
expires_in: 300
live-events:
aws_endpoint: http://kinesis.canvaslms.docker
kinesis_stream_name: live-events
live-events-subscription-service:
app-host: "http://les.docker"
sad-panda: null
math-man:
base_url: 'http://mathman.docker'
use_for_svg: 'false'
use_for_mml: 'false'
rich-content-service:
app-host: "rce.docker"
# tree
config:
# service
canvas:
# environment
address-book:
app-host: "http://address-book.docker"
secret: "opensesame"
canvas:
encryption-secret: "astringthatisactually32byteslong"
signing-secret: "astringthatisactually32byteslong"
live-events:
aws_endpoint: http://kinesis.canvaslms.docker
kinesis_stream_name: live-events
live-events-subscription-service:
app-host: "http://les.docker"
sad-panda: null
math-man:
base_url: 'http://mathman.docker'
use_for_svg: 'false'
use_for_mml: 'false'
rich-content-service:
app-host: "rce.docker"
# another service
inst-fs:
app-host: "http://inst-fs.docker"
secret: "super-sekret-value"
private:
canvas:
ha_cache.yml: |
cache_store: ha_store
servers:
- redis://localhost/2
# keep stale data for up to 1 week in the cache
race_condition_ttl: 604800
# how long it might take to recompute a cache value
# before the lock times out and another process is
# allowed to write it
lock_timeout: 5
# how long before a cache entry is considered stale
expires_in: 300

23
doc/docker/consul.md Normal file
View File

@ -0,0 +1,23 @@
# Consul
[Consul](https://www.consul.io/) is a service discovery and configuration
management system from Hashicorp. Canvas currently only uses the configuration
management k/v store. Generally speaking you won't need to run Consul during
development (or even small production deployments) because we have full
configuration support through `config/dynamic_settings.yml`.
For those who need to run Consul in development we've provided a docker-compose
override file to start up a Consul server and a rake task to pre-populate the
KV store from the values in `config/dynamic_settings.yml`. This rake task
(`canvas:seed_consul`) will traverse the tree found in the config file and write
the values found to the KV store, if a value already exists it will not be
overwritten. Due to the change in population mechanisms we have also enabled
persistence in the Consul container so users don't have to constantly refresh
the values in the KV store.
## Enabling Consul
To enable use of Consul with Docker there are three things that you need to do.
1. Add `docker-compose/consul.override.yml` to your `COMPOSE_FILE` env var.
2. Un-comment the development consul configuration in `config/consul.yml`
3. Run the rake task to pre-populate the config values in consul: `docker-compose run --rm web bin/rake canvas:seed_consul`

View File

@ -5,21 +5,45 @@
# shaped like the example below, one key for the related set of data,
# and a hash of key/value pairs (no nesting)
development:
address-book:
app-host: "http://address-book.docker"
secret: "opensesame"
live-events-subscription-service:
app-host: "http://les.docker"
sad-panda: null
rich-content-service:
app-host: "rce.docker"
canvas:
encryption-secret: "astringthatisactually32byteslong"
signing-secret: "astringthatisactually32byteslong"
live-events:
aws_endpoint: http://kinesis.canvaslms.docker
kinesis_stream_name: live-events
math-man:
base_url: 'http://mathman.docker'
use_for_svg: 'false'
use_for_mml: 'false'
# tree
config:
# service
canvas:
# prefix
address-book:
app-host: "http://address-book.docker"
secret: "opensesame"
canvas:
encryption-secret: "astringthatisactually32byteslong"
signing-secret: "astringthatisactually32byteslong"
live-events:
aws_endpoint: http://kinesis.canvaslms.docker
kinesis_stream_name: live-events
live-events-subscription-service:
app-host: "http://les.docker"
sad-panda: null
math-man:
base_url: 'http://mathman.docker'
use_for_svg: 'false'
use_for_mml: 'false'
rich-content-service:
app-host: "rce.docker"
# another service
inst-fs:
app-host: "http://inst-fs.docker"
secret: "super-sekret-value"
private:
canvas:
ha_cache.yml: |
cache_store: ha_store
servers:
- redis://redis/2
# keep stale data for up to 1 week in the cache
race_condition_ttl: 604800
# how long it might take to recompute a cache value
# before the lock times out and another process is
# allowed to write it
lock_timeout: 5
# how long before a cache entry is considered stale
expires_in: 300

View File

@ -8,8 +8,13 @@ services:
- consul
consul:
image: consul:0.7.2
command: agent -dev -client 0.0.0.0 -datacenter canvas-dev -node canvas-consul -bootstrap
image: consul:0.7.5
command: agent -server -client 0.0.0.0 -datacenter canvas-dev -node canvas-consul -bootstrap -ui
environment:
GOMAXPROCS: "2"
VIRTUAL_PORT: 8500
volumes:
- consul_data:/consul/data
volumes:
consul_data: {}

View File

@ -101,7 +101,8 @@ module Canvas
kv_client: kv_client)
else
proxy = root_fallback_proxy
proxy = proxy.for_prefix(service) if service && service != :canvas
proxy = proxy.for_prefix(tree)
proxy = proxy.for_prefix(service)
proxy = proxy.for_prefix(prefix) if prefix
proxy
end

View File

@ -119,6 +119,22 @@ namespace :canvas do
ENV["COMPILE_ASSETS_API_DOCS"] = "0"
Rake::Task['canvas:compile_assets'].invoke
end
desc "Load config/dynamic_settings.yml into the configured consul cluster"
task :seed_consul => [:environment] do
def load_tree(root, tree)
tree.each do |node, subtree|
key = [root, node].compact.join('/')
if Hash === subtree
load_tree(key, subtree)
else
Imperium::KV.put(key, subtree, cas: 0)
end
end
end
load_tree(nil, ConfigFile.load('dynamic_settings'))
end
end
namespace :lint do

View File

@ -106,8 +106,25 @@ module Canvas
end
context "when consul is not configured" do
let(:data) {
{
config: {
canvas: {foo: {bar: 'baz'}},
frobozz: {some: {thing: 'magic'}}
},
private: {
canvas: {zab: {rab: 'oof'}}
}
}
}
before do
DynamicSettings.config = nil
DynamicSettings.fallback_data = data
end
after do
DynamicSettings.fallback_data = nil
end
it 'must return an empty FallbackProxy when fallback data is also unconfigured' do
@ -117,37 +134,34 @@ module Canvas
end
it 'must return a FallbackProxy with configured fallback data' do
DynamicSettings.fallback_data = {'foo' => {bar: 'baz'}}
proxy = DynamicSettings.find('foo')
proxy = DynamicSettings.find('foo', tree: 'config', service: 'canvas')
expect(proxy).to be_a(DynamicSettings::FallbackProxy)
expect(proxy[:bar]).to eq 'baz'
end
it 'must treat a non-canvas service as a prefix' do
DynamicSettings.fallback_data = {'service' => {'foo' => {bar: 'baz'}}}
proxy = DynamicSettings.find('foo', service: 'service')
expect(proxy[:bar]).to eq 'baz'
it 'must default the the config tree' do
proxy = DynamicSettings.find('foo', service: 'canvas')
expect(proxy['bar']).to eq 'baz'
end
it 'must ignore a nil service' do
DynamicSettings.fallback_data = {'foo' => {bar: 'baz'}}
proxy = DynamicSettings.find('foo', service: nil)
expect(proxy[:bar]).to eq 'baz'
it 'must handle an alternate tree' do
proxy = DynamicSettings.find('zab', tree: 'private', service: 'canvas')
expect(proxy['rab']).to eq 'oof'
end
it 'must not treat explicit canvas service as a prefix' do
DynamicSettings.fallback_data = {
'foo' => {bar: 'baz'},
'canvas' => {'foo' => {bar: 'quux'}}
}
proxy = DynamicSettings.find('foo', service: :canvas)
expect(proxy[:bar]).to eq 'baz'
it 'must default to the canvas service' do
proxy = DynamicSettings.find('foo', tree: 'config')
expect(proxy['bar']).to eq 'baz'
end
it 'must accept an alternate service' do
proxy = DynamicSettings.find('some', tree: 'config', service: 'frobozz')
expect(proxy['thing']).to eq 'magic'
end
it 'must ignore a nil prefix' do
DynamicSettings.fallback_data = {'service' => {'foo' => 'bar'}}
proxy = DynamicSettings.find(service: 'service')
expect(proxy['foo']).to eq 'bar'
proxy = DynamicSettings.find(tree: 'config', service: 'canvas')
expect(proxy.for_prefix('foo')['bar']).to eq 'baz'
end
end
end

View File

@ -29,8 +29,12 @@ describe MathMan do
before do
@original_fallback = Canvas::DynamicSettings.fallback_data
Canvas::DynamicSettings.fallback_data = {
'math-man': {
base_url: service_url,
config: {
canvas: {
'math-man': {
base_url: service_url,
}
}
}
}
PluginSetting.create!(

View File

@ -115,8 +115,12 @@ describe "RequestContextGenerator" do
Thread.current[:context] = nil
Canvas::DynamicSettings.reset_cache!
Canvas::DynamicSettings.fallback_data = {
canvas: {
'signing-secret' => shared_secret
config: {
canvas: {
canvas: {
'signing-secret' => shared_secret
}
}
}
}
env['HTTP_X_REQUEST_CONTEXT_ID'] = Canvas::Security.base64_encode(remote_request_context_id)