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  }