github.com/splucs/witchcraft-go-server@v1.7.0/integration/runtime_test.go (about) 1 // Copyright (c) 2018 Palantir Technologies. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package integration 16 17 import ( 18 "context" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path" 23 "testing" 24 "time" 25 26 "github.com/nmiyake/pkg/dirs" 27 "github.com/palantir/pkg/httpserver" 28 "github.com/palantir/witchcraft-go-logging/wlog" 29 "github.com/palantir/witchcraft-go-server/config" 30 "github.com/palantir/witchcraft-go-server/status" 31 "github.com/palantir/witchcraft-go-server/witchcraft" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 "gopkg.in/yaml.v2" 35 ) 36 37 type testRuntimeConfig struct { 38 config.Runtime `yaml:",inline"` 39 SecretGreeting string `yaml:"secret-greeting"` 40 Exclamations int `yaml:"exclamations"` 41 } 42 43 // TestRuntimeReloadWithEncryptedConfig verifies behavior of refreshable configuration. 44 // 1. assert that the configuration printed at startup is that passed to the server's InitFunc 45 // 2. assert that removing the configuration does not send an update with empty values. 46 // 3. assert that writing a changed configuration does trigger subscribers to the refreshable. 47 func TestRuntimeReloadWithEncryptedConfig(t *testing.T) { 48 testDir, cleanup, err := dirs.TempDir("", "") 49 require.NoError(t, err) 50 defer cleanup() 51 52 wd, err := os.Getwd() 53 require.NoError(t, err) 54 defer func() { 55 err := os.Chdir(wd) 56 require.NoError(t, err) 57 }() 58 59 err = os.Chdir(testDir) 60 require.NoError(t, err) 61 62 port, err := httpserver.AvailablePort() 63 require.NoError(t, err) 64 65 err = os.MkdirAll("var/conf", 0755) 66 require.NoError(t, err) 67 68 installCfgYml := fmt.Sprintf(`product-name: %s 69 use-console-log: true 70 server: 71 address: localhost 72 port: %d 73 context-path: %s`, 74 productName, port, basePath) 75 err = ioutil.WriteFile(installYML, []byte(installCfgYml), 0644) 76 require.NoError(t, err) 77 78 const ecvKey = `AES:Nu2OInDbOHhXCNqqt1yyDuPwZwaJrSjV+IAypbZhw6Y=` 79 err = ioutil.WriteFile("var/conf/encrypted-config-value.key", []byte(ecvKey), 0644) 80 require.NoError(t, err) 81 82 cfg1 := testRuntimeConfig{SecretGreeting: "hello, world!", Exclamations: 3} 83 const cfg1YML = ` 84 secret-greeting: ${enc:/pSQ0v8R3QR8WOLnxoAWTsnI6kkjGgQMbqFcU9UC+LxStdGbfg1i3R9mlVZjEuXuecVG5AK1Sq109YxUcg==} 85 exclamations: 3 86 ` 87 cfg2 := testRuntimeConfig{SecretGreeting: "hello, world!", Exclamations: 4} 88 const cfg2YML = ` 89 secret-greeting: ${enc:/pSQ0v8R3QR8WOLnxoAWTsnI6kkjGgQMbqFcU9UC+LxStdGbfg1i3R9mlVZjEuXuecVG5AK1Sq109YxUcg==} 90 exclamations: 4 91 ` 92 err = ioutil.WriteFile(runtimeYML, []byte(cfg1YML), 0644) 93 require.NoError(t, err) 94 95 var currCfg testRuntimeConfig 96 97 server := witchcraft.NewServer(). 98 WithRuntimeConfigType(testRuntimeConfig{}). 99 WithDisableGoRuntimeMetrics(). 100 WithSelfSignedCertificate(). 101 WithInitFunc(func(ctx context.Context, info witchcraft.InitInfo) (cleanupFn func(), rErr error) { 102 setCfg := func(cfgI interface{}) { 103 cfg, ok := cfgI.(testRuntimeConfig) 104 if !ok { 105 panic(fmt.Errorf("unable to cast runtime config of type %T to testRuntimeConfig", cfgI)) 106 } 107 currCfg = cfg 108 } 109 setCfg(info.RuntimeConfig.Current()) 110 info.RuntimeConfig.Subscribe(setCfg) 111 return nil, nil 112 }) 113 114 serverChan := make(chan error) 115 go func() { 116 serverChan <- server.Start() 117 }() 118 119 select { 120 case err := <-serverChan: 121 require.NoError(t, err) 122 default: 123 } 124 125 ready := <-waitForTestServerReady(port, path.Join(basePath, status.LivenessEndpoint), 5*time.Second) 126 if !ready { 127 errMsg := "timed out waiting for server to start" 128 select { 129 case err := <-serverChan: 130 errMsg = fmt.Sprintf("%s: %+v", errMsg, err) 131 } 132 require.Fail(t, errMsg) 133 } 134 135 defer func() { 136 require.NoError(t, server.Close()) 137 }() 138 139 // Assert our configuration was set to the initial values 140 assert.Equal(t, cfg1, currCfg) 141 142 // Remove file and assert that we do not change the stored config 143 err = os.Remove(runtimeYML) 144 require.NoError(t, err) 145 time.Sleep(100 * time.Millisecond) 146 assert.Equal(t, cfg1, currCfg) 147 148 // Update config and assert that our subscription overwrites the value 149 err = ioutil.WriteFile(runtimeYML, []byte(cfg2YML), 0644) 150 require.NoError(t, err) 151 time.Sleep(100 * time.Millisecond) 152 assert.Equal(t, cfg2, currCfg) 153 } 154 155 // TestRuntimeReloadWithNilLoggerConfig verifies that reloading runtime configuration with nil logger config works. 156 func TestRuntimeReloadWithNilLoggerConfig(t *testing.T) { 157 testDir, cleanup, err := dirs.TempDir("", "") 158 require.NoError(t, err) 159 defer cleanup() 160 161 wd, err := os.Getwd() 162 require.NoError(t, err) 163 defer func() { 164 err := os.Chdir(wd) 165 require.NoError(t, err) 166 }() 167 168 err = os.Chdir(testDir) 169 require.NoError(t, err) 170 171 port, err := httpserver.AvailablePort() 172 require.NoError(t, err) 173 174 err = os.MkdirAll("var/conf", 0755) 175 require.NoError(t, err) 176 177 runtimeConfigWithLoggingYML, err := yaml.Marshal(config.Runtime{ 178 LoggerConfig: &config.LoggerConfig{ 179 Level: wlog.DebugLevel, 180 }, 181 }) 182 require.NoError(t, err) 183 184 err = ioutil.WriteFile(runtimeYML, runtimeConfigWithLoggingYML, 0644) 185 require.NoError(t, err) 186 187 runtimeConfigUpdatedChan := make(chan struct{}) 188 189 server := witchcraft.NewServer(). 190 WithInstallConfig(config.Install{ 191 ProductName: productName, 192 UseConsoleLog: true, 193 Server: config.Server{ 194 Address: "localhost", 195 Port: port, 196 ContextPath: basePath, 197 }, 198 }). 199 WithDisableGoRuntimeMetrics(). 200 WithSelfSignedCertificate(). 201 WithInitFunc(func(ctx context.Context, info witchcraft.InitInfo) (cleanupFn func(), rErr error) { 202 info.RuntimeConfig.Subscribe(func(cfgI interface{}) { 203 runtimeConfigUpdatedChan <- struct{}{} 204 }) 205 return nil, nil 206 }) 207 208 serverChan := make(chan error) 209 go func() { 210 serverChan <- server.Start() 211 }() 212 213 select { 214 case err := <-serverChan: 215 require.NoError(t, err) 216 default: 217 } 218 219 ready := <-waitForTestServerReady(port, path.Join(basePath, status.LivenessEndpoint), 5*time.Second) 220 if !ready { 221 errMsg := "timed out waiting for server to start" 222 select { 223 case err := <-serverChan: 224 errMsg = fmt.Sprintf("%s: %+v", errMsg, err) 225 } 226 require.Fail(t, errMsg) 227 } 228 229 defer func() { 230 require.NoError(t, server.Close()) 231 }() 232 233 err = ioutil.WriteFile(runtimeYML, []byte(""), 0644) 234 require.NoError(t, err) 235 236 select { 237 case <-runtimeConfigUpdatedChan: 238 break 239 case <-time.After(5 * time.Second): 240 assert.Fail(t, "timed out waiting for runtime configuration to be updated") 241 } 242 }