github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/featuretests/application_config_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package featuretests
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/cmd/cmdtesting"
    10  	"github.com/juju/collections/set"
    11  	jc "github.com/juju/testing/checkers"
    12  	"github.com/juju/utils"
    13  	gc "gopkg.in/check.v1"
    14  	"gopkg.in/juju/charm.v6"
    15  	"gopkg.in/juju/names.v2"
    16  	"gopkg.in/yaml.v2"
    17  
    18  	"github.com/juju/juju/api/uniter"
    19  	"github.com/juju/juju/cmd/juju/application"
    20  	jujutesting "github.com/juju/juju/juju/testing"
    21  	"github.com/juju/juju/state"
    22  )
    23  
    24  type ApplicationConfigSuite struct {
    25  	jujutesting.JujuConnSuite
    26  
    27  	appName string
    28  	charm   *state.Charm
    29  	apiUnit *uniter.Unit
    30  
    31  	settingKeys set.Strings
    32  }
    33  
    34  func (s *ApplicationConfigSuite) assertApplicationDeployed(c *gc.C) {
    35  	// Create application with all available config field types [currently string, int, boolean, float]
    36  	// where each type has 3 settings:
    37  	// * one with a default;
    38  	// * one with no default;
    39  	// * one will be set to a value at application deploy.
    40  	s.appName = "appconfig"
    41  	s.charm = s.AddTestingCharm(c, s.appName)
    42  
    43  	// Deploy application with custom config overwriting desired settings.
    44  	app, err := s.State.AddApplication(state.AddApplicationArgs{
    45  		Name:             s.appName,
    46  		Charm:            s.charm,
    47  		EndpointBindings: nil,
    48  		CharmConfig: map[string]interface{}{
    49  			"stroverwrite":     "test value",
    50  			"intoverwrite":     1620,
    51  			"floatoverwrite":   2.1,
    52  			"booleanoverwrite": false,
    53  			// nil values supplied by the user used to be a problem, bug#1667199
    54  			"booleandefault": nil,
    55  			"floatdefault":   nil,
    56  			"intdefault":     nil,
    57  			"strdefault":     nil,
    58  		},
    59  	})
    60  	c.Assert(err, jc.ErrorIsNil)
    61  
    62  	unit, err := app.AddUnit(state.AddUnitParams{})
    63  	c.Assert(err, jc.ErrorIsNil)
    64  	unit.SetCharmURL(s.charm.URL())
    65  
    66  	password, err := utils.RandomPassword()
    67  	c.Assert(err, jc.ErrorIsNil)
    68  	err = unit.SetPassword(password)
    69  	c.Assert(err, jc.ErrorIsNil)
    70  
    71  	st := s.OpenAPIAs(c, unit.Tag(), password)
    72  	uniteer, err := st.Uniter()
    73  	c.Assert(err, jc.ErrorIsNil)
    74  	c.Assert(uniteer, gc.NotNil)
    75  
    76  	s.apiUnit, err = uniteer.Unit(unit.Tag().(names.UnitTag))
    77  	c.Assert(err, jc.ErrorIsNil)
    78  
    79  	// Ensure both outputs have all charm config keys
    80  	s.settingKeys = set.NewStrings()
    81  	for k := range s.charm.Config().Options {
    82  		s.settingKeys.Add(k)
    83  	}
    84  }
    85  
    86  func (s *ApplicationConfigSuite) configCommandOutput(c *gc.C, args ...string) string {
    87  	context, err := cmdtesting.RunCommand(c, application.NewConfigCommand(), args...)
    88  	c.Assert(err, jc.ErrorIsNil)
    89  	return cmdtesting.Stdout(context)
    90  }
    91  
    92  func (s *ApplicationConfigSuite) getHookOutput(c *gc.C) charm.Settings {
    93  	settings, err := s.apiUnit.ConfigSettings()
    94  	c.Assert(err, jc.ErrorIsNil)
    95  	return settings
    96  }
    97  
    98  // The primary of objective of this test is to ensure
    99  // that both 'juju get' as well as unit in a hook context, uniter.unit, agree
   100  // on all returned settings and values.
   101  // These implementations are separate and cannot be re-factored. However,
   102  // since the logic and expected output is equivalent, these should be modified in sync.
   103  func (s *ApplicationConfigSuite) TestConfigAndConfigGetReturnAllCharmSettings(c *gc.C) {
   104  	// initial deploy with custom settings
   105  	s.assertApplicationDeployed(c)
   106  	s.assertSameConfigOutput(c, initialConfig)
   107  
   108  	// use 'juju config foo=' to change values
   109  	s.configCommandOutput(c, s.appName,
   110  		"booleandefault=false",
   111  		"booleannodefault=true",
   112  		"booleanoverwrite=true", //charm default
   113  		"floatdefault=7.2",
   114  		"floatnodefault=10.2",
   115  		"floatoverwrite=11.1", //charm default
   116  		"intdefault=22",
   117  		"intnodefault=11",
   118  		"intoverwrite=111", //charm default
   119  		"strdefault=not",
   120  		"strnodefault=maybe",
   121  		"stroverwrite=me",
   122  	)
   123  	s.assertSameConfigOutput(c, updatedConfig)
   124  
   125  	// 'juju config --reset' to reset settings to charm default
   126  	s.configCommandOutput(c, s.appName, "--reset",
   127  		"booleandefault,booleannodefault,booleanoverwrite,floatdefault,"+
   128  			"floatnodefault,floatoverwrite,intdefault,intnodefault,intoverwrite,"+
   129  			"strdefault,strnodefault,stroverwrite")
   130  	s.assertSameConfigOutput(c, resetConfig)
   131  }
   132  
   133  func (s *ApplicationConfigSuite) TestConfigNoValueSingleSetting(c *gc.C) {
   134  	appName := "appconfigsingle"
   135  	charm := s.AddTestingCharm(c, appName)
   136  	_, err := s.State.AddApplication(state.AddApplicationArgs{
   137  		Name:  appName,
   138  		Charm: charm,
   139  	})
   140  	c.Assert(err, jc.ErrorIsNil)
   141  
   142  	// use 'juju config foo' to see values
   143  	for option := range charm.Config().Options {
   144  		output := s.configCommandOutput(c, appName, option)
   145  		c.Assert(output, gc.Equals, "")
   146  	}
   147  	// set value to be something so that we can check newline added
   148  	s.configCommandOutput(c, appName, "stremptydefault=a")
   149  	output := s.configCommandOutput(c, appName, "stremptydefault")
   150  	c.Assert(output, gc.Equals, "a")
   151  }
   152  
   153  func (s *ApplicationConfigSuite) assertSameConfigOutput(c *gc.C, expectedValues settingsMap) {
   154  	s.assertJujuConfigOutput(c, s.configCommandOutput(c, s.appName), expectedValues)
   155  	s.assertHookOutput(c, s.getHookOutput(c), expectedValues)
   156  }
   157  
   158  func (s *ApplicationConfigSuite) assertHookOutput(c *gc.C, obtained charm.Settings, expected settingsMap) {
   159  	c.Assert(len(obtained), gc.Equals, len(expected))
   160  	c.Assert(len(obtained), gc.Equals, len(s.settingKeys))
   161  	for name, aSetting := range expected {
   162  		c.Assert(s.settingKeys.Contains(name), jc.IsTrue)
   163  		// due to awesome float64/int parsing confusion, it's actually safer to ensure that
   164  		// values' string representations match
   165  		c.Assert(fmt.Sprintf("%v", obtained[name]), gc.DeepEquals, fmt.Sprintf("%v", aSetting.Value))
   166  	}
   167  }
   168  
   169  func (s *ApplicationConfigSuite) assertJujuConfigOutput(c *gc.C, jujuConfigOutput string, expected settingsMap) {
   170  	var appSettings ApplicationSetting
   171  	err := yaml.Unmarshal([]byte(jujuConfigOutput), &appSettings)
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	obtained := appSettings.Settings
   174  
   175  	c.Assert(len(obtained), gc.Equals, len(expected))
   176  	c.Assert(len(obtained), gc.Equals, len(s.settingKeys))
   177  	for name, aSetting := range expected {
   178  		c.Assert(s.settingKeys.Contains(name), jc.IsTrue)
   179  		c.Assert(obtained[name].Value, gc.Equals, aSetting.Value)
   180  		c.Assert(obtained[name].Source, gc.Equals, aSetting.Source)
   181  	}
   182  }
   183  
   184  type configSetting struct {
   185  	Value  interface{}
   186  	Source string
   187  }
   188  
   189  type settingsMap map[string]configSetting
   190  
   191  var (
   192  	initialConfig = settingsMap{
   193  		"booleandefault":   {true, "default"},
   194  		"booleannodefault": {nil, "unset"},
   195  		"booleanoverwrite": {false, "user"},
   196  		"floatdefault":     {4.2, "default"},
   197  		"floatnodefault":   {nil, "unset"},
   198  		"floatoverwrite":   {2.1, "user"},
   199  		"intdefault":       {42, "default"},
   200  		"intnodefault":     {nil, "unset"},
   201  		"intoverwrite":     {1620, "user"},
   202  		"strdefault":       {"charm default", "default"},
   203  		"strnodefault":     {nil, "unset"},
   204  		"stroverwrite":     {"test value", "user"},
   205  	}
   206  	updatedConfig = settingsMap{
   207  		"booleandefault":   {false, "user"},
   208  		"booleannodefault": {true, "user"},
   209  		"booleanoverwrite": {true, "default"},
   210  		"floatdefault":     {7.2, "user"},
   211  		"floatnodefault":   {10.2, "user"},
   212  		"floatoverwrite":   {11.1, "default"},
   213  		"intdefault":       {22, "user"},
   214  		"intnodefault":     {11, "user"},
   215  		"intoverwrite":     {111, "default"},
   216  		"strdefault":       {"not", "user"},
   217  		"strnodefault":     {"maybe", "user"},
   218  		"stroverwrite":     {"me", "user"},
   219  	}
   220  	resetConfig = settingsMap{
   221  		"booleandefault":   {true, "default"},
   222  		"booleannodefault": {nil, "unset"},
   223  		"booleanoverwrite": {true, "default"},
   224  		"floatdefault":     {4.2, "default"},
   225  		"floatnodefault":   {nil, "unset"},
   226  		"floatoverwrite":   {11.1, "default"},
   227  		"intdefault":       {42, "default"},
   228  		"intnodefault":     {nil, "unset"},
   229  		"intoverwrite":     {111, "default"},
   230  		"strdefault":       {"charm default", "default"},
   231  		"strnodefault":     {nil, "unset"},
   232  		"stroverwrite":     {"overwrite me", "default"},
   233  	}
   234  )
   235  
   236  type TestSetting struct {
   237  	Default     interface{} `yaml:"default"`
   238  	Description string      `yaml:"description"`
   239  	Source      string      `yaml:"source"`
   240  	Type        string      `yaml:"type"`
   241  	Value       interface{} `yaml:"value"`
   242  }
   243  
   244  type ApplicationSetting struct {
   245  	Application string                 `yaml:"application"`
   246  	Charm       string                 `yaml:"charm"`
   247  	Settings    map[string]TestSetting `yaml:"settings"`
   248  }