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  }