github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/environ/environ_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package environ_test 5 6 import ( 7 "context" 8 "reflect" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/worker/v3/workertest" 15 gc "gopkg.in/check.v1" 16 17 "github.com/juju/juju/cloud" 18 "github.com/juju/juju/environs" 19 environscloudspec "github.com/juju/juju/environs/cloudspec" 20 coretesting "github.com/juju/juju/testing" 21 "github.com/juju/juju/worker/environ" 22 ) 23 24 type TrackerSuite struct { 25 coretesting.BaseSuite 26 } 27 28 var _ = gc.Suite(&TrackerSuite{}) 29 30 func (s *TrackerSuite) validConfig(observer environ.ConfigObserver) environ.Config { 31 if observer == nil { 32 observer = &runContext{} 33 } 34 return environ.Config{ 35 Observer: observer, 36 NewEnvironFunc: newMockEnviron, 37 Logger: loggo.GetLogger("test"), 38 } 39 } 40 41 func (s *TrackerSuite) TestValidateObserver(c *gc.C) { 42 config := s.validConfig(nil) 43 config.Observer = nil 44 s.testValidate(c, config, func(err error) { 45 c.Check(err, jc.Satisfies, errors.IsNotValid) 46 c.Check(err, gc.ErrorMatches, "nil Observer not valid") 47 }) 48 } 49 50 func (s *TrackerSuite) TestValidateNewEnvironFunc(c *gc.C) { 51 config := s.validConfig(nil) 52 config.NewEnvironFunc = nil 53 s.testValidate(c, config, func(err error) { 54 c.Check(err, jc.Satisfies, errors.IsNotValid) 55 c.Check(err, gc.ErrorMatches, "nil NewEnvironFunc not valid") 56 }) 57 } 58 59 func (s *TrackerSuite) TestValidateLogger(c *gc.C) { 60 config := s.validConfig(nil) 61 config.Logger = nil 62 s.testValidate(c, config, func(err error) { 63 c.Check(err, jc.Satisfies, errors.IsNotValid) 64 c.Check(err, gc.ErrorMatches, "nil Logger not valid") 65 }) 66 } 67 68 func (s *TrackerSuite) testValidate(c *gc.C, config environ.Config, check func(err error)) { 69 err := config.Validate() 70 check(err) 71 72 tracker, err := environ.NewTracker(config) 73 c.Check(tracker, gc.IsNil) 74 check(err) 75 } 76 77 func (s *TrackerSuite) TestModelConfigFails(c *gc.C) { 78 fix := &fixture{ 79 observerErrs: []error{ 80 errors.New("no you"), 81 }, 82 } 83 fix.Run(c, func(context *runContext) { 84 tracker, err := environ.NewTracker(s.validConfig(context)) 85 c.Check(err, gc.ErrorMatches, "retrieving model config: no you") 86 c.Check(tracker, gc.IsNil) 87 context.CheckCallNames(c, "ModelConfig") 88 }) 89 } 90 91 func (s *TrackerSuite) TestModelConfigInvalid(c *gc.C) { 92 fix := &fixture{} 93 fix.Run(c, func(runContext *runContext) { 94 config := s.validConfig(runContext) 95 config.NewEnvironFunc = func(context.Context, environs.OpenParams) (environs.Environ, error) { 96 return nil, errors.NotValidf("config") 97 } 98 tracker, err := environ.NewTracker(config) 99 c.Check(err, gc.ErrorMatches, 100 `creating environ for model \"testmodel\" \(deadbeef-0bad-400d-8000-4b1d0d06f00d\): config not valid`) 101 c.Check(tracker, gc.IsNil) 102 runContext.CheckCallNames(c, "ModelConfig", "CloudSpec") 103 }) 104 } 105 106 func (s *TrackerSuite) TestModelConfigValid(c *gc.C) { 107 fix := &fixture{ 108 initialConfig: coretesting.Attrs{ 109 "name": "this-particular-name", 110 }, 111 } 112 fix.Run(c, func(context *runContext) { 113 tracker, err := environ.NewTracker(s.validConfig(context)) 114 c.Assert(err, jc.ErrorIsNil) 115 defer workertest.CleanKill(c, tracker) 116 117 gotEnviron := tracker.Environ() 118 c.Assert(gotEnviron, gc.NotNil) 119 c.Check(gotEnviron.Config().Name(), gc.Equals, "this-particular-name") 120 }) 121 } 122 123 func (s *TrackerSuite) TestCloudSpec(c *gc.C) { 124 cloudSpec := environscloudspec.CloudSpec{ 125 Name: "foo", 126 Type: "bar", 127 Region: "baz", 128 } 129 fix := &fixture{initialSpec: cloudSpec} 130 fix.Run(c, func(runContext *runContext) { 131 config := s.validConfig(runContext) 132 config.NewEnvironFunc = func(_ context.Context, args environs.OpenParams) (environs.Environ, error) { 133 c.Assert(args.Cloud, jc.DeepEquals, cloudSpec) 134 return nil, errors.NotValidf("cloud spec") 135 } 136 tracker, err := environ.NewTracker(config) 137 c.Check(err, gc.ErrorMatches, 138 `creating environ for model \"testmodel\" \(deadbeef-0bad-400d-8000-4b1d0d06f00d\): cloud spec not valid`) 139 c.Check(tracker, gc.IsNil) 140 runContext.CheckCallNames(c, "ModelConfig", "CloudSpec") 141 }) 142 } 143 144 func (s *TrackerSuite) TestWatchFails(c *gc.C) { 145 fix := &fixture{ 146 observerErrs: []error{ 147 nil, nil, errors.New("grrk splat"), 148 }, 149 } 150 fix.Run(c, func(context *runContext) { 151 tracker, err := environ.NewTracker(s.validConfig(context)) 152 c.Assert(err, jc.ErrorIsNil) 153 defer workertest.DirtyKill(c, tracker) 154 155 err = workertest.CheckKilled(c, tracker) 156 c.Check(err, gc.ErrorMatches, 157 `model \"testmodel\" \(deadbeef-0bad-400d-8000-4b1d0d06f00d\): watching environ config: grrk splat`) 158 context.CheckCallNames(c, "ModelConfig", "CloudSpec", "WatchForModelConfigChanges") 159 }) 160 } 161 162 func (s *TrackerSuite) TestModelConfigWatchCloses(c *gc.C) { 163 fix := &fixture{} 164 fix.Run(c, func(context *runContext) { 165 tracker, err := environ.NewTracker(s.validConfig(context)) 166 c.Assert(err, jc.ErrorIsNil) 167 defer workertest.DirtyKill(c, tracker) 168 169 context.CloseModelConfigNotify() 170 err = workertest.CheckKilled(c, tracker) 171 c.Check(err, gc.ErrorMatches, 172 `model \"testmodel\" \(deadbeef-0bad-400d-8000-4b1d0d06f00d\): environ config watch closed`) 173 context.CheckCallNames(c, "ModelConfig", "CloudSpec", "WatchForModelConfigChanges", "WatchCloudSpecChanges") 174 }) 175 } 176 177 func (s *TrackerSuite) TestCloudSpecWatchCloses(c *gc.C) { 178 fix := &fixture{} 179 fix.Run(c, func(context *runContext) { 180 tracker, err := environ.NewTracker(s.validConfig(context)) 181 c.Assert(err, jc.ErrorIsNil) 182 defer workertest.DirtyKill(c, tracker) 183 184 context.CloseCloudSpecNotify() 185 err = workertest.CheckKilled(c, tracker) 186 c.Check(err, gc.ErrorMatches, 187 `model \"testmodel\" \(deadbeef-0bad-400d-8000-4b1d0d06f00d\): cloud watch closed`) 188 context.CheckCallNames(c, "ModelConfig", "CloudSpec", "WatchForModelConfigChanges", "WatchCloudSpecChanges") 189 }) 190 } 191 192 func (s *TrackerSuite) TestWatchedModelConfigFails(c *gc.C) { 193 fix := &fixture{ 194 observerErrs: []error{ 195 nil, nil, nil, nil, errors.New("blam ouch"), 196 }, 197 } 198 fix.Run(c, func(context *runContext) { 199 tracker, err := environ.NewTracker(s.validConfig(context)) 200 c.Check(err, jc.ErrorIsNil) 201 defer workertest.DirtyKill(c, tracker) 202 203 context.SendModelConfigNotify() 204 err = workertest.CheckKilled(c, tracker) 205 c.Check(err, gc.ErrorMatches, 206 `model \"testmodel\" \(deadbeef-0bad-400d-8000-4b1d0d06f00d\): reading model config: blam ouch`) 207 }) 208 } 209 210 func (s *TrackerSuite) TestWatchedModelConfigIncompatible(c *gc.C) { 211 fix := &fixture{} 212 fix.Run(c, func(runContext *runContext) { 213 config := s.validConfig(runContext) 214 config.NewEnvironFunc = func(_ context.Context, args environs.OpenParams) (environs.Environ, error) { 215 env := &mockEnviron{cfg: args.Config} 216 env.SetErrors(nil, errors.New("SetConfig is broken")) 217 return env, nil 218 } 219 tracker, err := environ.NewTracker(config) 220 c.Check(err, jc.ErrorIsNil) 221 defer workertest.DirtyKill(c, tracker) 222 223 runContext.SendModelConfigNotify() 224 err = workertest.CheckKilled(c, tracker) 225 c.Check(err, gc.ErrorMatches, 226 `model \"testmodel\" \(deadbeef-0bad-400d-8000-4b1d0d06f00d\): updating environ config: SetConfig is broken`) 227 runContext.CheckCallNames(c, 228 "ModelConfig", "CloudSpec", "WatchForModelConfigChanges", "WatchCloudSpecChanges", "ModelConfig") 229 }) 230 } 231 232 func (s *TrackerSuite) TestWatchedModelConfigUpdates(c *gc.C) { 233 fix := &fixture{ 234 initialConfig: coretesting.Attrs{ 235 "name": "original-name", 236 }, 237 } 238 fix.Run(c, func(context *runContext) { 239 tracker, err := environ.NewTracker(s.validConfig(context)) 240 c.Check(err, jc.ErrorIsNil) 241 defer workertest.CleanKill(c, tracker) 242 243 context.SetConfig(c, coretesting.Attrs{ 244 "name": "updated-name", 245 }) 246 gotEnviron := tracker.Environ() 247 c.Assert(gotEnviron.Config().Name(), gc.Equals, "original-name") 248 249 timeout := time.After(coretesting.LongWait) 250 attempt := time.After(0) 251 context.SendModelConfigNotify() 252 for { 253 select { 254 case <-attempt: 255 name := gotEnviron.Config().Name() 256 if name == "original-name" { 257 attempt = time.After(coretesting.ShortWait) 258 continue 259 } 260 c.Check(name, gc.Equals, "updated-name") 261 case <-timeout: 262 c.Fatalf("timed out waiting for environ to be updated") 263 } 264 break 265 } 266 }) 267 } 268 269 func (s *TrackerSuite) TestWatchedCloudSpecUpdates(c *gc.C) { 270 fix := &fixture{ 271 initialSpec: environscloudspec.CloudSpec{Name: "cloud", Type: "lxd"}, 272 } 273 fix.Run(c, func(context *runContext) { 274 tracker, err := environ.NewTracker(s.validConfig(context)) 275 c.Check(err, jc.ErrorIsNil) 276 defer workertest.CleanKill(c, tracker) 277 278 context.SetCloudSpec(c, environscloudspec.CloudSpec{Name: "lxd", Type: "lxd", Endpoint: "http://api"}) 279 gotEnviron := tracker.Environ().(*mockEnviron) 280 c.Assert(gotEnviron.CloudSpec(), jc.DeepEquals, fix.initialSpec) 281 282 timeout := time.After(coretesting.LongWait) 283 attempt := time.After(0) 284 context.SendCloudSpecNotify() 285 for { 286 select { 287 case <-attempt: 288 ep := gotEnviron.CloudSpec().Endpoint 289 if ep == "" { 290 attempt = time.After(coretesting.ShortWait) 291 continue 292 } 293 c.Check(ep, gc.Equals, "http://api") 294 case <-timeout: 295 c.Fatalf("timed out waiting for environ to be updated") 296 } 297 break 298 } 299 }) 300 } 301 302 func (s *TrackerSuite) TestWatchedCloudSpecCredentialsUpdates(c *gc.C) { 303 original := cloud.NewCredential( 304 cloud.UserPassAuthType, 305 map[string]string{ 306 "username": "user", 307 "password": "secret", 308 }, 309 ) 310 differentContent := cloud.NewCredential( 311 cloud.UserPassAuthType, 312 map[string]string{ 313 "username": "user", 314 "password": "not-secret-anymore", 315 }, 316 ) 317 fix := &fixture{ 318 initialSpec: environscloudspec.CloudSpec{Name: "cloud", Type: "lxd", Credential: &original}, 319 } 320 fix.Run(c, func(context *runContext) { 321 tracker, err := environ.NewTracker(s.validConfig(context)) 322 c.Check(err, jc.ErrorIsNil) 323 defer workertest.CleanKill(c, tracker) 324 325 context.SetCloudSpec(c, environscloudspec.CloudSpec{Name: "lxd", Type: "lxd", Credential: &differentContent}) 326 gotEnviron := tracker.Environ().(*mockEnviron) 327 c.Assert(gotEnviron.CloudSpec(), jc.DeepEquals, fix.initialSpec) 328 329 timeout := time.After(coretesting.LongWait) 330 attempt := time.After(0) 331 context.SendCloudSpecNotify() 332 for { 333 select { 334 case <-attempt: 335 ep := gotEnviron.CloudSpec().Credential 336 if reflect.DeepEqual(ep, &original) { 337 attempt = time.After(coretesting.ShortWait) 338 continue 339 } 340 c.Check(reflect.DeepEqual(ep, &differentContent), jc.IsTrue) 341 case <-timeout: 342 c.Fatalf("timed out waiting for environ to be updated") 343 } 344 break 345 } 346 }) 347 }