github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/proxyupdater/proxyupdater_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package proxyupdater_test 5 6 import ( 7 "io/ioutil" 8 "os" 9 "path" 10 "path/filepath" 11 "runtime" 12 "strings" 13 "time" 14 15 "github.com/juju/errors" 16 "github.com/juju/loggo" 17 "github.com/juju/os/series" 18 "github.com/juju/packaging/commands" 19 pacconfig "github.com/juju/packaging/config" 20 "github.com/juju/proxy" 21 jc "github.com/juju/testing/checkers" 22 gc "gopkg.in/check.v1" 23 "gopkg.in/juju/worker.v1" 24 "gopkg.in/juju/worker.v1/workertest" 25 26 proxyupdaterapi "github.com/juju/juju/api/proxyupdater" 27 "github.com/juju/juju/core/watcher" 28 coretesting "github.com/juju/juju/testing" 29 "github.com/juju/juju/worker/proxyupdater" 30 ) 31 32 type ProxyUpdaterSuite struct { 33 coretesting.BaseSuite 34 35 api *fakeAPI 36 proxyEnvFile string 37 proxySystemdFile string 38 detectedSettings proxy.Settings 39 inProcSettings chan proxy.Settings 40 config proxyupdater.Config 41 } 42 43 var _ = gc.Suite(&ProxyUpdaterSuite{}) 44 45 func newNotAWatcher() notAWatcher { 46 return notAWatcher{workertest.NewFakeWatcher(2, 2)} 47 } 48 49 type notAWatcher struct { 50 workertest.NotAWatcher 51 } 52 53 func (w notAWatcher) Changes() watcher.NotifyChannel { 54 return w.NotAWatcher.Changes() 55 } 56 57 type fakeAPI struct { 58 proxies proxyupdaterapi.ProxyConfiguration 59 Err error 60 Watcher *notAWatcher 61 } 62 63 func NewFakeAPI() *fakeAPI { 64 f := &fakeAPI{} 65 return f 66 } 67 68 func (api fakeAPI) ProxyConfig() (proxyupdaterapi.ProxyConfiguration, error) { 69 return api.proxies, api.Err 70 } 71 72 func (api fakeAPI) WatchForProxyConfigAndAPIHostPortChanges() (watcher.NotifyWatcher, error) { 73 if api.Watcher == nil { 74 w := newNotAWatcher() 75 api.Watcher = &w 76 } 77 return api.Watcher, nil 78 } 79 80 func (s *ProxyUpdaterSuite) SetUpTest(c *gc.C) { 81 s.BaseSuite.SetUpTest(c) 82 s.api = NewFakeAPI() 83 84 // Make buffer large for tests that never look at the settings. 85 s.inProcSettings = make(chan proxy.Settings, 1000) 86 87 directory := c.MkDir() 88 s.proxySystemdFile = filepath.Join(directory, "systemd.file") 89 s.proxyEnvFile = filepath.Join(directory, "env.file") 90 logger := loggo.GetLogger("test.proxyupdater") 91 logger.SetLogLevel(loggo.TRACE) 92 s.config = proxyupdater.Config{ 93 SystemdFiles: []string{s.proxySystemdFile}, 94 EnvFiles: []string{s.proxyEnvFile}, 95 API: s.api, 96 InProcessUpdate: func(newSettings proxy.Settings) error { 97 select { 98 case s.inProcSettings <- newSettings: 99 case <-time.After(coretesting.LongWait): 100 panic("couldn't send settings on inProcSettings channel") 101 } 102 return nil 103 }, 104 Logger: logger, 105 } 106 s.PatchValue(&pacconfig.AptProxyConfigFile, path.Join(directory, "juju-apt-proxy")) 107 } 108 109 func (s *ProxyUpdaterSuite) TearDownTest(c *gc.C) { 110 s.BaseSuite.TearDownTest(c) 111 if s.api.Watcher != nil { 112 s.api.Watcher.Close() 113 } 114 } 115 116 func (s *ProxyUpdaterSuite) waitProxySettings(c *gc.C, expected proxy.Settings) { 117 maxWait := time.After(coretesting.LongWait) 118 var ( 119 inProcSettings, envSettings proxy.Settings 120 gotInProc, gotEnv bool 121 ) 122 for { 123 select { 124 case <-maxWait: 125 c.Fatalf("timeout while waiting for proxy settings to change") 126 return 127 case inProcSettings = <-s.inProcSettings: 128 if c.Check(inProcSettings, gc.Equals, expected) { 129 gotInProc = true 130 } 131 case <-time.After(coretesting.ShortWait): 132 envSettings = proxy.DetectProxies() 133 if envSettings == expected { 134 gotEnv = true 135 } else { 136 if envSettings != s.detectedSettings { 137 c.Logf("proxy settings are \n%#v, should be \n%#v, still waiting", envSettings, expected) 138 } 139 s.detectedSettings = envSettings 140 } 141 } 142 if gotEnv && gotInProc { 143 break 144 } 145 } 146 } 147 148 func (s *ProxyUpdaterSuite) waitForFile(c *gc.C, filename, expected string) { 149 //TODO(bogdanteleaga): Find a way to test this on windows 150 if runtime.GOOS == "windows" { 151 c.Skip("Proxy settings are written to the registry on windows") 152 } 153 maxWait := time.After(coretesting.LongWait) 154 for { 155 select { 156 case <-maxWait: 157 c.Fatalf("timeout while waiting for proxy settings to change") 158 return 159 case <-time.After(10 * time.Millisecond): 160 fileContent, err := ioutil.ReadFile(filename) 161 if os.IsNotExist(err) { 162 continue 163 } 164 c.Assert(err, jc.ErrorIsNil) 165 if string(fileContent) != expected { 166 c.Logf("file content not matching, still waiting") 167 continue 168 } 169 return 170 } 171 } 172 } 173 174 func (s *ProxyUpdaterSuite) assertNoFile(c *gc.C, filename string) { 175 //TODO(bogdanteleaga): Find a way to test this on windows 176 if runtime.GOOS == "windows" { 177 c.Skip("Proxy settings are written to the registry on windows") 178 } 179 maxWait := time.After(coretesting.ShortWait) 180 for { 181 select { 182 case <-maxWait: 183 return 184 case <-time.After(10 * time.Millisecond): 185 _, err := os.Stat(filename) 186 if err == nil { 187 c.Fatalf("file %s exists", filename) 188 } 189 } 190 } 191 } 192 193 func (s *ProxyUpdaterSuite) TestRunStop(c *gc.C) { 194 updater, err := proxyupdater.NewWorker(s.config) 195 c.Assert(err, jc.ErrorIsNil) 196 workertest.CleanKill(c, updater) 197 } 198 199 func (s *ProxyUpdaterSuite) useLegacyConfig(c *gc.C) (proxy.Settings, proxy.Settings) { 200 s.api.proxies = proxyupdaterapi.ProxyConfiguration{ 201 LegacyProxy: proxy.Settings{ 202 Http: "http legacy proxy", 203 Https: "https legacy proxy", 204 Ftp: "ftp legacy proxy", 205 NoProxy: "localhost,no legacy proxy", 206 }, 207 APTProxy: proxy.Settings{ 208 Http: "http://apt.http.proxy", 209 Https: "https://apt.https.proxy", 210 Ftp: "ftp://apt.ftp.proxy", 211 }, 212 } 213 214 return s.api.proxies.LegacyProxy, s.api.proxies.APTProxy 215 } 216 217 func (s *ProxyUpdaterSuite) useJujuConfig(c *gc.C) (proxy.Settings, proxy.Settings) { 218 s.api.proxies = proxyupdaterapi.ProxyConfiguration{ 219 JujuProxy: proxy.Settings{ 220 Http: "http juju proxy", 221 Https: "https juju proxy", 222 Ftp: "ftp juju proxy", 223 NoProxy: "localhost,no juju proxy", 224 }, 225 APTProxy: proxy.Settings{ 226 Http: "http://apt.http.proxy", 227 Https: "https://apt.https.proxy", 228 Ftp: "ftp://apt.ftp.proxy", 229 }, 230 } 231 232 return s.api.proxies.JujuProxy, s.api.proxies.APTProxy 233 } 234 235 func (s *ProxyUpdaterSuite) TestInitialStateLegacyProxy(c *gc.C) { 236 proxySettings, aptProxySettings := s.useLegacyConfig(c) 237 238 updater, err := proxyupdater.NewWorker(s.config) 239 c.Assert(err, jc.ErrorIsNil) 240 defer worker.Stop(updater) 241 242 s.waitProxySettings(c, proxySettings) 243 s.waitForFile(c, s.proxyEnvFile, proxySettings.AsScriptEnvironment()) 244 s.waitForFile(c, s.proxySystemdFile, proxySettings.AsSystemdDefaultEnv()) 245 246 paccmder, err := commands.NewPackageCommander(series.MustHostSeries()) 247 c.Assert(err, jc.ErrorIsNil) 248 s.waitForFile(c, pacconfig.AptProxyConfigFile, paccmder.ProxyConfigContents(aptProxySettings)+"\n") 249 } 250 251 func (s *ProxyUpdaterSuite) TestInitialStateJujuProxy(c *gc.C) { 252 proxySettings, aptProxySettings := s.useJujuConfig(c) 253 254 updater, err := proxyupdater.NewWorker(s.config) 255 c.Assert(err, jc.ErrorIsNil) 256 defer worker.Stop(updater) 257 258 s.waitProxySettings(c, proxySettings) 259 var empty proxy.Settings 260 // The environment files are written, but with empty content. 261 // This keeps the symlinks working. 262 s.waitForFile(c, s.proxyEnvFile, empty.AsScriptEnvironment()) 263 s.waitForFile(c, s.proxySystemdFile, empty.AsSystemdDefaultEnv()) 264 265 paccmder, err := commands.NewPackageCommander(series.MustHostSeries()) 266 c.Assert(err, jc.ErrorIsNil) 267 s.waitForFile(c, pacconfig.AptProxyConfigFile, paccmder.ProxyConfigContents(aptProxySettings)+"\n") 268 } 269 270 func (s *ProxyUpdaterSuite) TestEnvironmentVariablesLegacyProxy(c *gc.C) { 271 setenv := func(proxy, value string) { 272 os.Setenv(proxy, value) 273 os.Setenv(strings.ToUpper(proxy), value) 274 } 275 setenv("http_proxy", "foo") 276 setenv("https_proxy", "foo") 277 setenv("ftp_proxy", "foo") 278 setenv("no_proxy", "foo") 279 280 proxySettings, _ := s.useLegacyConfig(c) 281 updater, err := proxyupdater.NewWorker(s.config) 282 c.Assert(err, jc.ErrorIsNil) 283 defer worker.Stop(updater) 284 s.waitProxySettings(c, proxySettings) 285 286 assertEnv := func(proxy, value string) { 287 c.Assert(os.Getenv(proxy), gc.Equals, value) 288 c.Assert(os.Getenv(strings.ToUpper(proxy)), gc.Equals, value) 289 } 290 assertEnv("http_proxy", proxySettings.Http) 291 assertEnv("https_proxy", proxySettings.Https) 292 assertEnv("ftp_proxy", proxySettings.Ftp) 293 assertEnv("no_proxy", proxySettings.NoProxy) 294 } 295 296 func (s *ProxyUpdaterSuite) TestEnvironmentVariablesJujuProxy(c *gc.C) { 297 setenv := func(proxy, value string) { 298 os.Setenv(proxy, value) 299 os.Setenv(strings.ToUpper(proxy), value) 300 } 301 setenv("http_proxy", "foo") 302 setenv("https_proxy", "foo") 303 setenv("ftp_proxy", "foo") 304 setenv("no_proxy", "foo") 305 306 proxySettings, _ := s.useJujuConfig(c) 307 updater, err := proxyupdater.NewWorker(s.config) 308 c.Assert(err, jc.ErrorIsNil) 309 defer worker.Stop(updater) 310 s.waitProxySettings(c, proxySettings) 311 312 assertEnv := func(proxy, value string) { 313 c.Assert(os.Getenv(proxy), gc.Equals, value) 314 c.Assert(os.Getenv(strings.ToUpper(proxy)), gc.Equals, value) 315 } 316 assertEnv("http_proxy", proxySettings.Http) 317 assertEnv("https_proxy", proxySettings.Https) 318 assertEnv("ftp_proxy", proxySettings.Ftp) 319 assertEnv("no_proxy", proxySettings.NoProxy) 320 } 321 322 func (s *ProxyUpdaterSuite) TestExternalFuncCalled(c *gc.C) { 323 324 // Called for both legacy and juju proxy values 325 externalProxySet := func() proxy.Settings { 326 updated := make(chan proxy.Settings) 327 done := make(chan struct{}) 328 s.config.ExternalUpdate = func(values proxy.Settings) error { 329 select { 330 case updated <- values: 331 case <-done: 332 } 333 return nil 334 } 335 updater, err := proxyupdater.NewWorker(s.config) 336 c.Assert(err, jc.ErrorIsNil) 337 defer worker.Stop(updater) 338 // We need to close done before stopping the worker, so the 339 // defer comes after the worker stop. 340 defer close(done) 341 342 select { 343 case <-time.After(time.Second): 344 c.Fatal("function not called") 345 case externalSettings := <-updated: 346 return externalSettings 347 } 348 return proxy.Settings{} 349 } 350 351 proxySettings, _ := s.useLegacyConfig(c) 352 externalSettings := externalProxySet() 353 c.Assert(externalSettings, jc.DeepEquals, proxySettings) 354 355 proxySettings, _ = s.useJujuConfig(c) 356 externalSettings = externalProxySet() 357 c.Assert(externalSettings, jc.DeepEquals, proxySettings) 358 } 359 360 func (s *ProxyUpdaterSuite) TestErrorSettingInProcessLogs(c *gc.C) { 361 proxySettings, _ := s.useJujuConfig(c) 362 363 s.config.InProcessUpdate = func(newSettings proxy.Settings) error { 364 select { 365 case s.inProcSettings <- newSettings: 366 case <-time.After(coretesting.LongWait): 367 panic("couldn't send settings on inProcSettings channel") 368 } 369 return errors.New("gone daddy gone") 370 } 371 372 var logWriter loggo.TestWriter 373 c.Assert(loggo.RegisterWriter("proxyupdater-tests", &logWriter), jc.ErrorIsNil) 374 defer func() { 375 loggo.RemoveWriter("proxyupdater-tests") 376 logWriter.Clear() 377 }() 378 379 updater, err := proxyupdater.NewWorker(s.config) 380 c.Assert(err, jc.ErrorIsNil) 381 s.waitProxySettings(c, proxySettings) 382 workertest.CleanKill(c, updater) 383 384 var foundMessage bool 385 expectedMessage := "error updating in-process proxy settings: gone daddy gone" 386 for _, entry := range logWriter.Log() { 387 if entry.Level == loggo.ERROR && strings.Contains(entry.Message, expectedMessage) { 388 foundMessage = true 389 break 390 } 391 } 392 c.Assert(foundMessage, jc.IsTrue) 393 } 394 395 func nextCall(c *gc.C, calls <-chan []string) []string { 396 select { 397 case call := <-calls: 398 return call 399 case <-time.After(coretesting.LongWait): 400 c.Fatalf("run func not called") 401 } 402 panic("unreachable") 403 } 404 405 func (s *ProxyUpdaterSuite) TestSnapProxySetNoneSet(c *gc.C) { 406 if runtime.GOOS == "windows" { 407 c.Skip("snap settings not handled on windows") 408 } 409 410 logger := s.config.Logger 411 calls := make(chan []string) 412 s.config.RunFunc = func(in string, cmd string, args ...string) (string, error) { 413 logger.Debugf("RunFunc(%q, %q, %#v)", in, cmd, args) 414 calls <- append([]string{in, cmd}, args...) 415 return "", nil 416 } 417 418 s.api.proxies = proxyupdaterapi.ProxyConfiguration{} 419 420 updater, err := proxyupdater.NewWorker(s.config) 421 c.Assert(err, jc.ErrorIsNil) 422 defer workertest.CleanKill(c, updater) 423 424 // The worker doesn't precheck any of the snap proxy values, as it is expected 425 // that the set call is cheap. Every time the worker starts, we call set for the current 426 // values. 427 c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"", "snap", "set", "core", 428 "proxy.http=", 429 "proxy.https=", 430 "proxy.store=", 431 }) 432 } 433 434 func (s *ProxyUpdaterSuite) TestSnapProxySet(c *gc.C) { 435 if runtime.GOOS == "windows" { 436 c.Skip("snap settings not handled on windows") 437 } 438 439 logger := s.config.Logger 440 calls := make(chan []string) 441 s.config.RunFunc = func(in string, cmd string, args ...string) (string, error) { 442 logger.Debugf("RunFunc(%q, %q, %#v)", in, cmd, args) 443 calls <- append([]string{in, cmd}, args...) 444 return "", nil 445 } 446 447 s.api.proxies = proxyupdaterapi.ProxyConfiguration{ 448 SnapProxy: proxy.Settings{ 449 Http: "http://snap-proxy", 450 Https: "https://snap-proxy", 451 }, 452 } 453 454 updater, err := proxyupdater.NewWorker(s.config) 455 c.Assert(err, jc.ErrorIsNil) 456 defer workertest.CleanKill(c, updater) 457 458 // The snap store is set to the empty string because as the agent is starting 459 // and it doesn't check to see what the store was set to, so to be sure, it just 460 // calls the set value. 461 c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"", "snap", "set", "core", 462 "proxy.http=http://snap-proxy", 463 "proxy.https=https://snap-proxy", 464 "proxy.store=", 465 }) 466 } 467 468 func (s *ProxyUpdaterSuite) TestSnapStoreProxy(c *gc.C) { 469 if runtime.GOOS == "windows" { 470 c.Skip("snap settings not handled on windows") 471 } 472 473 logger := s.config.Logger 474 calls := make(chan []string) 475 s.config.RunFunc = func(in string, cmd string, args ...string) (string, error) { 476 logger.Debugf("RunFunc(%q, %q, %#v)", in, cmd, args) 477 calls <- append([]string{in, cmd}, args...) 478 return "", nil 479 } 480 481 s.api.proxies = proxyupdaterapi.ProxyConfiguration{ 482 SnapStoreProxyId: "42", 483 SnapStoreProxyAssertions: "please trust us", 484 } 485 486 updater, err := proxyupdater.NewWorker(s.config) 487 c.Assert(err, jc.ErrorIsNil) 488 defer workertest.CleanKill(c, updater) 489 490 // The http and https proxy values are set to be empty as it is the first pass through. 491 c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"", "snap", "set", "core", 492 "proxy.http=", 493 "proxy.https=", 494 "proxy.store=42", 495 }) 496 c.Assert(nextCall(c, calls), jc.DeepEquals, []string{"please trust us", "snap", "ack", "/dev/stdin"}) 497 }