github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/endpoint_bindings_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"github.com/juju/charm/v12"
     8  	"github.com/juju/errors"
     9  	"github.com/juju/testing"
    10  	jc "github.com/juju/testing/checkers"
    11  	"go.uber.org/mock/gomock"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	"github.com/juju/juju/core/network"
    15  	"github.com/juju/juju/state"
    16  	"github.com/juju/juju/state/mocks"
    17  )
    18  
    19  type bindingsSuite struct {
    20  	ConnSuite
    21  
    22  	oldMeta     *charm.Meta
    23  	oldDefaults map[string]string
    24  	newMeta     *charm.Meta
    25  	newDefaults map[string]string
    26  
    27  	clientSpace *state.Space
    28  	appsSpace   *state.Space
    29  	barbSpace   *state.Space
    30  	dbSpace     *state.Space
    31  }
    32  
    33  var _ = gc.Suite(&bindingsSuite{})
    34  
    35  func (s *bindingsSuite) SetUpTest(c *gc.C) {
    36  	s.ConnSuite.SetUpTest(c)
    37  
    38  	const dummyCharmWithOneOfEachRelationTypeAndExtraBindings = `
    39  name: dummy
    40  summary: "That's a dummy charm with one relation of each type and extra-bindings."
    41  description: "This is a longer description."
    42  provides:
    43    foo1:
    44      interface: phony
    45  requires:
    46    bar1:
    47      interface: fake
    48  peers:
    49    self:
    50      interface: dummy
    51  extra-bindings:
    52    one-extra:
    53  `
    54  	oldCharm := s.AddMetaCharm(c, "dummy", dummyCharmWithOneOfEachRelationTypeAndExtraBindings, 1)
    55  	s.oldMeta = oldCharm.Meta()
    56  	s.oldDefaults = map[string]string{
    57  		"":          network.AlphaSpaceId,
    58  		"foo1":      network.AlphaSpaceId,
    59  		"bar1":      network.AlphaSpaceId,
    60  		"self":      network.AlphaSpaceId,
    61  		"one-extra": network.AlphaSpaceId,
    62  	}
    63  
    64  	const dummyCharmWithTwoOfEachRelationTypeAndNoExtraBindings = `
    65  name: dummy
    66  summary: "That's a dummy charm with 2 relations for each type."
    67  description: "This is a longer description."
    68  provides:
    69    foo1:
    70      interface: phony
    71    foo2:
    72      interface: secret
    73  requires:
    74    bar2: real
    75    bar3:
    76      interface: cool
    77  peers:
    78    self:
    79      interface: dummy
    80    me: peer
    81  `
    82  	newCharm := s.AddMetaCharm(c, "dummy", dummyCharmWithTwoOfEachRelationTypeAndNoExtraBindings, 2)
    83  	s.newMeta = newCharm.Meta()
    84  	s.newDefaults = map[string]string{
    85  		"foo1": network.AlphaSpaceId,
    86  		"foo2": network.AlphaSpaceId,
    87  		"bar2": network.AlphaSpaceId,
    88  		"bar3": network.AlphaSpaceId,
    89  		"self": network.AlphaSpaceId,
    90  		"me":   network.AlphaSpaceId,
    91  	}
    92  
    93  	// Add some spaces to use in bindings, but notably NOT the default space, as
    94  	// it should be always allowed.
    95  
    96  	var err error
    97  	s.clientSpace, err = s.State.AddSpace("client", "", nil, true)
    98  	c.Assert(err, jc.ErrorIsNil)
    99  	s.appsSpace, err = s.State.AddSpace("apps", "", nil, true)
   100  	c.Assert(err, jc.ErrorIsNil)
   101  	s.dbSpace, err = s.State.AddSpace("db", "", nil, true)
   102  	c.Assert(err, jc.ErrorIsNil)
   103  	s.barbSpace, err = s.State.AddSpace("barb3", "", nil, true)
   104  	c.Assert(err, jc.ErrorIsNil)
   105  }
   106  
   107  func (s *bindingsSuite) TestMergeBindings(c *gc.C) {
   108  	// The test cases below are not exhaustive, but just check basic
   109  	// functionality. Most of the logic is tested by calling application.SetCharm()
   110  	// in various ways.
   111  
   112  	for i, test := range []struct {
   113  		about                    string
   114  		mergeWithMap, currentMap map[string]string
   115  		meta                     *charm.Meta
   116  		updated                  map[string]string
   117  		modified                 bool
   118  	}{{
   119  		about:        "defaults used when both mergeWithMap and currentMap are nil",
   120  		mergeWithMap: nil,
   121  		currentMap:   nil,
   122  		meta:         s.oldMeta,
   123  		updated:      s.copyMap(s.oldDefaults),
   124  		modified:     true,
   125  	}, {
   126  		about:        "currentMap overrides defaults, mergeWithMap is nil",
   127  		mergeWithMap: nil,
   128  		currentMap: map[string]string{
   129  			"foo1": s.clientSpace.Id(),
   130  			"self": s.dbSpace.Id(),
   131  		},
   132  		meta: s.oldMeta,
   133  		updated: map[string]string{
   134  			"":          network.AlphaSpaceId,
   135  			"foo1":      s.clientSpace.Id(),
   136  			"bar1":      network.AlphaSpaceId,
   137  			"self":      s.dbSpace.Id(),
   138  			"one-extra": network.AlphaSpaceId,
   139  		},
   140  		modified: true,
   141  	}, {
   142  		about: "currentMap overrides defaults, mergeWithMap overrides currentMap",
   143  		mergeWithMap: map[string]string{
   144  			"":          network.AlphaSpaceId,
   145  			"foo1":      network.AlphaSpaceId,
   146  			"self":      s.dbSpace.Id(),
   147  			"bar1":      s.clientSpace.Id(),
   148  			"one-extra": s.appsSpace.Id(),
   149  		},
   150  		currentMap: map[string]string{
   151  			"foo1": s.clientSpace.Id(),
   152  			"bar1": s.dbSpace.Id(),
   153  		},
   154  		meta: s.oldMeta,
   155  		updated: map[string]string{
   156  			"":          network.AlphaSpaceId,
   157  			"foo1":      network.AlphaSpaceId,
   158  			"bar1":      s.clientSpace.Id(),
   159  			"self":      s.dbSpace.Id(),
   160  			"one-extra": s.appsSpace.Id(),
   161  		},
   162  		modified: true,
   163  	}, {
   164  		about: "mergeWithMap overrides defaults, currentMap is nil",
   165  		mergeWithMap: map[string]string{
   166  			"self": s.dbSpace.Id(),
   167  		},
   168  		currentMap: nil,
   169  		meta:       s.oldMeta,
   170  		updated: map[string]string{
   171  			"":          network.AlphaSpaceId,
   172  			"foo1":      network.AlphaSpaceId,
   173  			"bar1":      network.AlphaSpaceId,
   174  			"self":      s.dbSpace.Id(),
   175  			"one-extra": network.AlphaSpaceId,
   176  		},
   177  		modified: true,
   178  	}, {
   179  		about:        "obsolete entries in currentMap missing in defaults are removed",
   180  		mergeWithMap: nil,
   181  		currentMap: map[string]string{
   182  			"any-old-thing": s.dbSpace.Id(),
   183  			"self":          s.dbSpace.Id(),
   184  			"one-extra":     s.appsSpace.Id(),
   185  		},
   186  		meta: s.oldMeta,
   187  		updated: map[string]string{
   188  			"":          network.AlphaSpaceId,
   189  			"foo1":      network.AlphaSpaceId,
   190  			"bar1":      network.AlphaSpaceId,
   191  			"self":      s.dbSpace.Id(),
   192  			"one-extra": s.appsSpace.Id(),
   193  		},
   194  		modified: true,
   195  	}, {
   196  		about: "new endpoints use defaults unless specified in mergeWithMap, existing ones are kept",
   197  		mergeWithMap: map[string]string{
   198  			"foo2": s.dbSpace.Id(),
   199  			"me":   s.clientSpace.Id(),
   200  			"bar3": s.dbSpace.Id(),
   201  		},
   202  		currentMap: s.copyMap(s.oldDefaults),
   203  		meta:       s.newMeta,
   204  		updated: map[string]string{
   205  			"":     network.AlphaSpaceId,
   206  			"foo1": network.AlphaSpaceId,
   207  			"foo2": s.dbSpace.Id(),
   208  			"bar2": network.AlphaSpaceId,
   209  			"bar3": s.dbSpace.Id(),
   210  			"self": network.AlphaSpaceId,
   211  			"me":   s.clientSpace.Id(),
   212  		},
   213  		modified: true,
   214  	}, {
   215  		about: "new default supersedes old default",
   216  		mergeWithMap: map[string]string{
   217  			"":     s.clientSpace.Name(),
   218  			"bar3": s.barbSpace.Name(),
   219  		},
   220  		currentMap: map[string]string{
   221  			"":          s.appsSpace.Id(),
   222  			"foo1":      s.appsSpace.Id(),
   223  			"bar1":      s.dbSpace.Id(),
   224  			"self":      network.AlphaSpaceId,
   225  			"one-extra": s.barbSpace.Id(),
   226  		},
   227  		meta: s.newMeta,
   228  		updated: map[string]string{
   229  			"":     s.clientSpace.Id(),
   230  			"foo1": s.appsSpace.Id(),
   231  			"foo2": s.clientSpace.Id(),
   232  			"bar2": s.clientSpace.Id(),
   233  			"bar3": s.barbSpace.Id(),
   234  			"self": network.AlphaSpaceId,
   235  			"me":   s.clientSpace.Id(),
   236  		},
   237  		modified: true,
   238  	}, {
   239  		about: "new map one change",
   240  		mergeWithMap: map[string]string{
   241  			"self": s.barbSpace.Name(),
   242  		},
   243  		currentMap: map[string]string{
   244  			"":          s.appsSpace.Id(),
   245  			"foo1":      s.appsSpace.Id(),
   246  			"bar1":      s.dbSpace.Id(),
   247  			"self":      network.AlphaSpaceId,
   248  			"one-extra": s.clientSpace.Id(),
   249  		},
   250  		meta: s.oldMeta,
   251  		updated: map[string]string{
   252  			"":          s.appsSpace.Id(),
   253  			"foo1":      s.appsSpace.Id(),
   254  			"bar1":      s.dbSpace.Id(),
   255  			"self":      s.barbSpace.Id(),
   256  			"one-extra": s.clientSpace.Id(),
   257  		},
   258  		modified: true,
   259  	}, {
   260  		about:        "old unchanged but different key",
   261  		mergeWithMap: nil,
   262  		currentMap: map[string]string{
   263  			"":          s.appsSpace.Id(),
   264  			"bar1":      s.dbSpace.Id(),
   265  			"self":      network.AlphaSpaceId,
   266  			"lost":      s.clientSpace.Id(),
   267  			"one-extra": s.clientSpace.Id(),
   268  		},
   269  		meta: s.oldMeta,
   270  		updated: map[string]string{
   271  			"":          s.appsSpace.Id(),
   272  			"foo1":      s.appsSpace.Id(),
   273  			"bar1":      s.dbSpace.Id(),
   274  			"self":      network.AlphaSpaceId,
   275  			"one-extra": s.clientSpace.Id(),
   276  		},
   277  		modified: true,
   278  	}} {
   279  		c.Logf("test #%d: %s", i, test.about)
   280  		b, err := state.NewBindings(s.State, test.currentMap)
   281  		c.Assert(err, jc.ErrorIsNil)
   282  		isModified, err := b.Merge(test.mergeWithMap, test.meta)
   283  		c.Check(err, jc.ErrorIsNil)
   284  		c.Check(b.Map(), jc.DeepEquals, test.updated)
   285  		c.Check(isModified, gc.Equals, test.modified)
   286  	}
   287  }
   288  
   289  func (s *bindingsSuite) TestMergeWithModelConfigNonDefaultSpace(c *gc.C) {
   290  	err := s.Model.UpdateModelConfig(map[string]interface{}{"default-space": s.appsSpace.Name()}, nil)
   291  	c.Assert(err, jc.ErrorIsNil)
   292  
   293  	currentMap := map[string]string{
   294  		"foo1": s.clientSpace.Id(),
   295  		"self": s.dbSpace.Id(),
   296  	}
   297  	updated := map[string]string{
   298  		"":          s.appsSpace.Id(),
   299  		"foo1":      s.clientSpace.Id(),
   300  		"bar1":      s.appsSpace.Id(),
   301  		"self":      s.dbSpace.Id(),
   302  		"one-extra": s.appsSpace.Id(),
   303  	}
   304  
   305  	b, err := state.NewBindings(s.State, currentMap)
   306  	c.Assert(err, jc.ErrorIsNil)
   307  	isModified, err := b.Merge(nil, s.oldMeta)
   308  	c.Check(err, jc.ErrorIsNil)
   309  	c.Check(b.Map(), jc.DeepEquals, updated)
   310  	c.Check(isModified, gc.Equals, true)
   311  }
   312  
   313  func (s *bindingsSuite) TestDefaultEndpointBindingSpaceNotDefault(c *gc.C) {
   314  	err := s.Model.UpdateModelConfig(map[string]interface{}{"default-space": s.clientSpace.Name()}, nil)
   315  	c.Assert(err, jc.ErrorIsNil)
   316  	id, err := s.State.DefaultEndpointBindingSpace()
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	c.Assert(id, gc.Equals, s.clientSpace.Id())
   319  }
   320  
   321  func (s *bindingsSuite) TestDefaultEndpointBindingSpaceDefault(c *gc.C) {
   322  	id, err := s.State.DefaultEndpointBindingSpace()
   323  	c.Assert(err, jc.ErrorIsNil)
   324  	c.Assert(id, gc.Equals, network.AlphaSpaceId)
   325  }
   326  
   327  func (s *bindingsSuite) copyMap(input map[string]string) map[string]string {
   328  	output := make(map[string]string, len(input))
   329  	for key, value := range input {
   330  		output[key] = value
   331  	}
   332  	return output
   333  }
   334  
   335  var _ = gc.Suite(&bindingsMockSuite{})
   336  
   337  type bindingsMockSuite struct {
   338  	testing.IsolationSuite
   339  
   340  	endpointBinding *mocks.MockEndpointBinding
   341  }
   342  
   343  func (s *bindingsMockSuite) TestNewBindingsNilMap(c *gc.C) {
   344  	defer s.setup(c).Finish()
   345  	s.expectAllSpaceInfos(s.expectedSpaceInfos())
   346  
   347  	binding, err := state.NewBindings(s.endpointBinding, nil)
   348  	c.Assert(err, jc.ErrorIsNil)
   349  	c.Assert(binding, gc.NotNil)
   350  	c.Assert(binding.Map(), gc.DeepEquals, map[string]string{})
   351  }
   352  
   353  func (s *bindingsMockSuite) TestNewBindingsByID(c *gc.C) {
   354  	defer s.setup(c).Finish()
   355  	s.expectAllSpaceInfos(s.expectedSpaceInfos())
   356  
   357  	initial := map[string]string{
   358  		"db":      "2",
   359  		"testing": "5",
   360  		"empty":   network.AlphaSpaceId,
   361  	}
   362  
   363  	binding, err := state.NewBindings(s.endpointBinding, initial)
   364  	c.Assert(err, jc.ErrorIsNil)
   365  	c.Assert(binding, gc.NotNil)
   366  
   367  	c.Assert(binding.Map(), jc.DeepEquals, initial)
   368  }
   369  
   370  func (s *bindingsMockSuite) TestNewBindingsByName(c *gc.C) {
   371  	defer s.setup(c).Finish()
   372  	s.expectAllSpaceInfos(s.expectedSpaceInfos())
   373  
   374  	initial := map[string]string{
   375  		"db":      "two",
   376  		"testing": "42",
   377  		"empty":   network.AlphaSpaceName,
   378  	}
   379  
   380  	binding, err := state.NewBindings(s.endpointBinding, initial)
   381  	c.Assert(err, jc.ErrorIsNil)
   382  	c.Assert(binding, gc.NotNil)
   383  
   384  	expected := map[string]string{
   385  		"db":      "2",
   386  		"testing": "5",
   387  		"empty":   network.AlphaSpaceId,
   388  	}
   389  	c.Logf("%+v", binding.Map())
   390  	c.Assert(binding.Map(), jc.DeepEquals, expected)
   391  }
   392  
   393  func (s *bindingsMockSuite) TestNewBindingsNotFound(c *gc.C) {
   394  	defer s.setup(c).Finish()
   395  	s.expectAllSpaceInfos(s.expectedSpaceInfos())
   396  	initial := map[string]string{
   397  		"db":      "2",
   398  		"testing": "three",
   399  		"empty":   network.AlphaSpaceId,
   400  	}
   401  
   402  	binding, err := state.NewBindings(s.endpointBinding, initial)
   403  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   404  	c.Assert(binding, gc.IsNil)
   405  }
   406  
   407  func (s *bindingsMockSuite) TestMapWithSpaceNames(c *gc.C) {
   408  	defer s.setup(c).Finish()
   409  
   410  	infos := s.expectedSpaceInfos()
   411  	s.expectAllSpaceInfos(infos)
   412  
   413  	initial := map[string]string{
   414  		"db":      "2",
   415  		"testing": "3",
   416  		"empty":   network.AlphaSpaceId,
   417  	}
   418  
   419  	binding, err := state.NewBindings(s.endpointBinding, initial)
   420  	c.Assert(err, jc.ErrorIsNil)
   421  	c.Assert(binding, gc.NotNil)
   422  	withSpaceNames, err := binding.MapWithSpaceNames(infos)
   423  	c.Assert(err, jc.ErrorIsNil)
   424  
   425  	expected := map[string]string{
   426  		"db":      "two",
   427  		"testing": "three",
   428  		"empty":   network.AlphaSpaceName,
   429  	}
   430  	c.Assert(withSpaceNames, jc.DeepEquals, expected)
   431  }
   432  
   433  func (s *bindingsMockSuite) TestMapWithSpaceNamesWithNoLookup(c *gc.C) {
   434  	defer s.setup(c).Finish()
   435  
   436  	s.expectAllSpaceInfos(s.expectedSpaceInfos())
   437  
   438  	initial := map[string]string{
   439  		"db":      "2",
   440  		"testing": "3",
   441  		"empty":   network.AlphaSpaceId,
   442  	}
   443  
   444  	binding, err := state.NewBindings(s.endpointBinding, initial)
   445  	c.Assert(err, jc.ErrorIsNil)
   446  	c.Assert(binding, gc.NotNil)
   447  	_, err = binding.MapWithSpaceNames(nil)
   448  	c.Assert(err, gc.ErrorMatches, "*not valid*")
   449  }
   450  
   451  func (s *bindingsMockSuite) TestMapWithSpaceNamesWithNoBindings(c *gc.C) {
   452  	defer s.setup(c).Finish()
   453  
   454  	s.expectAllSpaceInfos(s.expectedSpaceInfos())
   455  
   456  	initial := map[string]string{}
   457  
   458  	binding, err := state.NewBindings(s.endpointBinding, initial)
   459  	c.Assert(err, jc.ErrorIsNil)
   460  	c.Assert(binding, gc.NotNil)
   461  	withSpaceNames, err := binding.MapWithSpaceNames(make(network.SpaceInfos, 0))
   462  	c.Assert(err, jc.ErrorIsNil)
   463  	c.Assert(withSpaceNames, gc.HasLen, 0)
   464  }
   465  
   466  func (s *bindingsMockSuite) TestMapWithSpaceNamesWithEmptyBindings(c *gc.C) {
   467  	defer s.setup(c).Finish()
   468  
   469  	s.expectAllSpaceInfos(s.expectedSpaceInfos())
   470  
   471  	initial := map[string]string{
   472  		"db":      "2",
   473  		"testing": "3",
   474  		"empty":   network.AlphaSpaceId,
   475  	}
   476  
   477  	binding, err := state.NewBindings(s.endpointBinding, initial)
   478  	c.Assert(err, jc.ErrorIsNil)
   479  	c.Assert(binding, gc.NotNil)
   480  	_, err = binding.MapWithSpaceNames(make(network.SpaceInfos, 0))
   481  	c.Assert(err, gc.ErrorMatches, "*not valid*")
   482  }
   483  
   484  func (s *bindingsMockSuite) expectedSpaceInfos() network.SpaceInfos {
   485  	return network.SpaceInfos{
   486  		{ID: network.AlphaSpaceId, Name: network.AlphaSpaceName},
   487  		{ID: "1", Name: "one"},
   488  		{ID: "2", Name: "two"},
   489  		{ID: "3", Name: "three"},
   490  		{ID: "4", Name: "four"},
   491  		{ID: "5", Name: "42"},
   492  	}
   493  }
   494  
   495  func (s *bindingsMockSuite) expectAllSpaceInfos(infos network.SpaceInfos) {
   496  	s.endpointBinding.EXPECT().AllSpaceInfos().Return(infos, nil)
   497  }
   498  
   499  func (s *bindingsMockSuite) setup(c *gc.C) *gomock.Controller {
   500  	ctrl := gomock.NewController(c)
   501  	s.endpointBinding = mocks.NewMockEndpointBinding(ctrl)
   502  	return ctrl
   503  }