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  }