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 }