github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/upgrades_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	jc "github.com/juju/testing/checkers"
     9  	gc "gopkg.in/check.v1"
    10  	"gopkg.in/mgo.v2"
    11  	"gopkg.in/mgo.v2/bson"
    12  	"gopkg.in/mgo.v2/txn"
    13  
    14  	"github.com/juju/juju/network"
    15  	"github.com/juju/juju/permission"
    16  	"github.com/juju/juju/status"
    17  	"github.com/juju/juju/testing"
    18  )
    19  
    20  type upgradesSuite struct {
    21  	internalStateSuite
    22  }
    23  
    24  var _ = gc.Suite(&upgradesSuite{})
    25  
    26  func (s *upgradesSuite) addLegacyDoc(c *gc.C, collName string, legacyDoc bson.M) {
    27  	ops := []txn.Op{{
    28  		C:      collName,
    29  		Id:     legacyDoc["_id"],
    30  		Assert: txn.DocMissing,
    31  		Insert: legacyDoc,
    32  	}}
    33  	err := s.state.runRawTransaction(ops)
    34  	c.Assert(err, jc.ErrorIsNil)
    35  }
    36  
    37  func (s *upgradesSuite) FindId(c *gc.C, coll *mgo.Collection, id interface{}, doc interface{}) {
    38  	err := coll.FindId(id).One(doc)
    39  	c.Assert(err, jc.ErrorIsNil)
    40  }
    41  
    42  func (s *upgradesSuite) removePreferredAddressFields(c *gc.C, machine *Machine) {
    43  	machinesCol, closer := s.state.getRawCollection(machinesC)
    44  	defer closer()
    45  
    46  	err := machinesCol.Update(
    47  		bson.D{{"_id", s.state.docID(machine.Id())}},
    48  		bson.D{{"$unset", bson.D{{"preferredpublicaddress", ""}}}},
    49  	)
    50  	c.Assert(err, jc.ErrorIsNil)
    51  	err = machinesCol.Update(
    52  		bson.D{{"_id", s.state.docID(machine.Id())}},
    53  		bson.D{{"$unset", bson.D{{"preferredprivateaddress", ""}}}},
    54  	)
    55  	c.Assert(err, jc.ErrorIsNil)
    56  }
    57  
    58  func (s *upgradesSuite) setPreferredAddressFields(c *gc.C, machine *Machine, addr string) {
    59  	machinesCol, closer := s.state.getRawCollection(machinesC)
    60  	defer closer()
    61  
    62  	stateAddr := fromNetworkAddress(network.NewAddress(addr), OriginUnknown)
    63  	err := machinesCol.Update(
    64  		bson.D{{"_id", s.state.docID(machine.Id())}},
    65  		bson.D{{"$set", bson.D{{"preferredpublicaddress", stateAddr}}}},
    66  	)
    67  	c.Assert(err, jc.ErrorIsNil)
    68  	err = machinesCol.Update(
    69  		bson.D{{"_id", s.state.docID(machine.Id())}},
    70  		bson.D{{"$set", bson.D{{"preferredprivateaddress", stateAddr}}}},
    71  	)
    72  	c.Assert(err, jc.ErrorIsNil)
    73  }
    74  
    75  func assertMachineAddresses(c *gc.C, machine *Machine, publicAddress, privateAddress string) {
    76  	err := machine.Refresh()
    77  	c.Assert(err, jc.ErrorIsNil)
    78  	addr, err := machine.PublicAddress()
    79  	if publicAddress != "" {
    80  		c.Assert(err, jc.ErrorIsNil)
    81  	} else {
    82  		c.Assert(err, jc.Satisfies, network.IsNoAddressError)
    83  	}
    84  	c.Assert(addr.Value, gc.Equals, publicAddress)
    85  	privAddr, err := machine.PrivateAddress()
    86  	if privateAddress != "" {
    87  		c.Assert(err, jc.ErrorIsNil)
    88  	} else {
    89  		c.Assert(err, jc.Satisfies, network.IsNoAddressError)
    90  	}
    91  	c.Assert(privAddr.Value, gc.Equals, privateAddress)
    92  }
    93  
    94  func (s *upgradesSuite) createMachinesWithAddresses(c *gc.C) []*Machine {
    95  	_, err := s.state.AddMachine("quantal", JobManageModel)
    96  	c.Assert(err, jc.ErrorIsNil)
    97  	_, err = s.state.AddMachines([]MachineTemplate{
    98  		{Series: "quantal", Jobs: []MachineJob{JobHostUnits}},
    99  		{Series: "quantal", Jobs: []MachineJob{JobHostUnits}},
   100  		{Series: "quantal", Jobs: []MachineJob{JobHostUnits}},
   101  	}...)
   102  	c.Assert(err, jc.ErrorIsNil)
   103  	machines, err := s.state.AllMachines()
   104  	c.Assert(err, jc.ErrorIsNil)
   105  	c.Assert(machines, gc.HasLen, 4)
   106  
   107  	m1 := machines[0]
   108  	m2 := machines[1]
   109  	m4 := machines[3]
   110  	err = m1.SetProviderAddresses(network.NewAddress("8.8.8.8"))
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	err = m2.SetMachineAddresses(network.NewAddress("10.0.0.1"))
   113  	c.Assert(err, jc.ErrorIsNil)
   114  	err = m2.SetProviderAddresses(network.NewAddress("10.0.0.2"), network.NewAddress("8.8.4.4"))
   115  	c.Assert(err, jc.ErrorIsNil)
   116  
   117  	// Attempting to set the addresses of a dead machine will fail, so we
   118  	// include a dead machine to make sure the upgrade step can cope.
   119  	err = m4.SetProviderAddresses(network.NewAddress("8.8.8.8"))
   120  	c.Assert(err, jc.ErrorIsNil)
   121  	err = m4.EnsureDead()
   122  	c.Assert(err, jc.ErrorIsNil)
   123  
   124  	// Delete the preferred address fields.
   125  	for _, machine := range machines {
   126  		s.removePreferredAddressFields(c, machine)
   127  	}
   128  	return machines
   129  }
   130  
   131  func (s *upgradesSuite) TestAddPreferredAddressesToMachines(c *gc.C) {
   132  	machines := s.createMachinesWithAddresses(c)
   133  	m1 := machines[0]
   134  	m2 := machines[1]
   135  	m3 := machines[2]
   136  
   137  	err := AddPreferredAddressesToMachines(s.state)
   138  	c.Assert(err, jc.ErrorIsNil)
   139  
   140  	assertMachineAddresses(c, m1, "8.8.8.8", "8.8.8.8")
   141  	assertMachineAddresses(c, m2, "8.8.4.4", "10.0.0.2")
   142  	assertMachineAddresses(c, m3, "", "")
   143  }
   144  
   145  func (s *upgradesSuite) TestAddPreferredAddressesToMachinesIdempotent(c *gc.C) {
   146  	machines := s.createMachinesWithAddresses(c)
   147  	m1 := machines[0]
   148  	m2 := machines[1]
   149  	m3 := machines[2]
   150  
   151  	err := AddPreferredAddressesToMachines(s.state)
   152  	c.Assert(err, jc.ErrorIsNil)
   153  
   154  	assertMachineAddresses(c, m1, "8.8.8.8", "8.8.8.8")
   155  	assertMachineAddresses(c, m2, "8.8.4.4", "10.0.0.2")
   156  	assertMachineAddresses(c, m3, "", "")
   157  
   158  	err = AddPreferredAddressesToMachines(s.state)
   159  	c.Assert(err, jc.ErrorIsNil)
   160  
   161  	assertMachineAddresses(c, m1, "8.8.8.8", "8.8.8.8")
   162  	assertMachineAddresses(c, m2, "8.8.4.4", "10.0.0.2")
   163  	assertMachineAddresses(c, m3, "", "")
   164  }
   165  
   166  func (s *upgradesSuite) TestAddPreferredAddressesToMachinesUpdatesExistingFields(c *gc.C) {
   167  	machines := s.createMachinesWithAddresses(c)
   168  	m1 := machines[0]
   169  	m2 := machines[1]
   170  	m3 := machines[2]
   171  	s.setPreferredAddressFields(c, m1, "1.1.2.2")
   172  	s.setPreferredAddressFields(c, m2, "1.1.2.2")
   173  	s.setPreferredAddressFields(c, m3, "1.1.2.2")
   174  
   175  	assertMachineInitial := func(m *Machine) {
   176  		err := m.Refresh()
   177  		c.Assert(err, jc.ErrorIsNil)
   178  		addr, err := m.PublicAddress()
   179  		c.Assert(err, jc.ErrorIsNil)
   180  		c.Assert(addr.Value, gc.Equals, "1.1.2.2")
   181  		addr, err = m.PrivateAddress()
   182  		c.Assert(err, jc.ErrorIsNil)
   183  		c.Assert(addr.Value, gc.Equals, "1.1.2.2")
   184  	}
   185  	assertMachineInitial(m1)
   186  	assertMachineInitial(m2)
   187  	assertMachineInitial(m3)
   188  
   189  	err := AddPreferredAddressesToMachines(s.state)
   190  	c.Assert(err, jc.ErrorIsNil)
   191  
   192  	assertMachineAddresses(c, m1, "8.8.8.8", "8.8.8.8")
   193  	assertMachineAddresses(c, m2, "8.8.4.4", "10.0.0.2")
   194  	assertMachineAddresses(c, m3, "", "")
   195  }
   196  
   197  func (s *upgradesSuite) readDocIDs(c *gc.C, coll, regex string) []string {
   198  	settings, closer := s.state.getRawCollection(coll)
   199  	defer closer()
   200  	var docs []bson.M
   201  	err := settings.Find(bson.D{{"_id", bson.D{{"$regex", regex}}}}).All(&docs)
   202  	c.Assert(err, jc.ErrorIsNil)
   203  	var actualDocIDs []string
   204  	for _, doc := range docs {
   205  		actualDocIDs = append(actualDocIDs, doc["_id"].(string))
   206  	}
   207  	return actualDocIDs
   208  }
   209  
   210  func (s *upgradesSuite) getDocMap(c *gc.C, docID, collection string) (map[string]interface{}, error) {
   211  	docMap := map[string]interface{}{}
   212  	coll, closer := s.state.getRawCollection(collection)
   213  	defer closer()
   214  	err := coll.Find(bson.D{{"_id", docID}}).One(&docMap)
   215  	return docMap, err
   216  }
   217  
   218  func unsetField(st *State, id, collection, field string) error {
   219  	return st.runTransaction(
   220  		[]txn.Op{{
   221  			C:      collection,
   222  			Id:     id,
   223  			Update: bson.D{{"$unset", bson.D{{field, nil}}}},
   224  		},
   225  		})
   226  }
   227  
   228  func setupMachineBoundStorageTests(c *gc.C, st *State) (*Machine, Volume, Filesystem, func() error) {
   229  	// Make an unprovisioned machine with storage for tests to use.
   230  	// TODO(axw) extend testing/factory to allow creating unprovisioned
   231  	// machines.
   232  	m, err := st.AddOneMachine(MachineTemplate{
   233  		Series: "quantal",
   234  		Jobs:   []MachineJob{JobHostUnits},
   235  		Volumes: []MachineVolumeParams{
   236  			{Volume: VolumeParams{Pool: "loop", Size: 2048}},
   237  		},
   238  		Filesystems: []MachineFilesystemParams{
   239  			{Filesystem: FilesystemParams{Pool: "rootfs", Size: 2048}},
   240  		},
   241  	})
   242  	c.Assert(err, jc.ErrorIsNil)
   243  
   244  	va, err := m.VolumeAttachments()
   245  	c.Assert(err, jc.ErrorIsNil)
   246  	c.Assert(va, gc.HasLen, 1)
   247  	v, err := st.Volume(va[0].Volume())
   248  	c.Assert(err, jc.ErrorIsNil)
   249  
   250  	fa, err := st.MachineFilesystemAttachments(m.MachineTag())
   251  	c.Assert(err, jc.ErrorIsNil)
   252  	c.Assert(fa, gc.HasLen, 1)
   253  	f, err := st.Filesystem(fa[0].Filesystem())
   254  	c.Assert(err, jc.ErrorIsNil)
   255  
   256  	return m, v, f, m.Destroy
   257  }
   258  
   259  func (s *upgradesSuite) TestAddFilesystemStatus(c *gc.C) {
   260  	_, _, filesystem, cleanup := setupMachineBoundStorageTests(c, s.state)
   261  	defer cleanup()
   262  
   263  	removeStatusDoc(c, s.state, filesystem)
   264  	_, err := filesystem.Status()
   265  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   266  	s.assertAddFilesystemStatus(c, filesystem, status.Pending)
   267  }
   268  
   269  func (s *upgradesSuite) TestAddFilesystemStatusDoesNotOverwrite(c *gc.C) {
   270  	_, _, filesystem, cleanup := setupMachineBoundStorageTests(c, s.state)
   271  	defer cleanup()
   272  
   273  	now := testing.ZeroTime()
   274  	sInfo := status.StatusInfo{
   275  		Status:  status.Destroying,
   276  		Message: "",
   277  		Since:   &now,
   278  	}
   279  	err := filesystem.SetStatus(sInfo)
   280  	c.Assert(err, jc.ErrorIsNil)
   281  	s.assertAddFilesystemStatus(c, filesystem, status.Destroying)
   282  }
   283  
   284  func (s *upgradesSuite) TestAddFilesystemStatusProvisioned(c *gc.C) {
   285  	_, _, filesystem, cleanup := setupMachineBoundStorageTests(c, s.state)
   286  	defer cleanup()
   287  
   288  	err := s.state.SetFilesystemInfo(filesystem.FilesystemTag(), FilesystemInfo{
   289  		FilesystemId: "fs",
   290  	})
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	removeStatusDoc(c, s.state, filesystem)
   293  	s.assertAddFilesystemStatus(c, filesystem, status.Attaching)
   294  }
   295  
   296  func (s *upgradesSuite) TestAddFilesystemStatusAttached(c *gc.C) {
   297  	machine, _, filesystem, cleanup := setupMachineBoundStorageTests(c, s.state)
   298  	defer cleanup()
   299  
   300  	err := machine.SetProvisioned("fake", "fake", nil)
   301  	c.Assert(err, jc.ErrorIsNil)
   302  
   303  	err = s.state.SetFilesystemInfo(filesystem.FilesystemTag(), FilesystemInfo{
   304  		FilesystemId: "fs",
   305  	})
   306  	c.Assert(err, jc.ErrorIsNil)
   307  
   308  	err = s.state.SetFilesystemAttachmentInfo(
   309  		machine.MachineTag(),
   310  		filesystem.FilesystemTag(),
   311  		FilesystemAttachmentInfo{},
   312  	)
   313  	c.Assert(err, jc.ErrorIsNil)
   314  
   315  	removeStatusDoc(c, s.state, filesystem)
   316  	s.assertAddFilesystemStatus(c, filesystem, status.Attached)
   317  }
   318  
   319  func (s *upgradesSuite) assertAddFilesystemStatus(c *gc.C, filesystem Filesystem, expect status.Status) {
   320  	err := AddFilesystemStatus(s.state)
   321  	c.Assert(err, jc.ErrorIsNil)
   322  
   323  	info, err := filesystem.Status()
   324  	c.Assert(err, jc.ErrorIsNil)
   325  	c.Assert(info.Status, gc.Equals, expect)
   326  }
   327  
   328  func removeStatusDoc(c *gc.C, st *State, g GlobalEntity) {
   329  	op := removeStatusOp(st, g.globalKey())
   330  	err := st.runTransaction([]txn.Op{op})
   331  	c.Assert(err, jc.ErrorIsNil)
   332  }
   333  
   334  func (s *upgradesSuite) TestMigrateSettingsSchema(c *gc.C) {
   335  	// Insert test documents.
   336  	settingsColl, closer := s.state.getRawCollection(settingsC)
   337  	defer closer()
   338  	err := settingsColl.Insert(
   339  		bson.D{
   340  			// Post-model-uuid migration, with no settings.
   341  			{"_id", "1"},
   342  			{"model-uuid", "model-uuid"},
   343  			{"txn-revno", int64(99)},
   344  			{"txn-queue", []string{}},
   345  		},
   346  		bson.D{
   347  			// Post-model-uuid migration, with settings. One
   348  			// of the settings is called "settings", and
   349  			// one "version".
   350  			{"_id", "2"},
   351  			{"model-uuid", "model-uuid"},
   352  			{"txn-revno", int64(99)},
   353  			{"txn-queue", []string{}},
   354  			{"settings", int64(123)},
   355  			{"version", "onetwothree"},
   356  		},
   357  		bson.D{
   358  			// Pre-model-uuid migration, with no settings.
   359  			{"_id", "3"},
   360  			{"txn-revno", int64(99)},
   361  			{"txn-queue", []string{}},
   362  		},
   363  		bson.D{
   364  			// Pre-model-uuid migration, with settings.
   365  			{"_id", "4"},
   366  			{"txn-revno", int64(99)},
   367  			{"txn-queue", []string{}},
   368  			{"settings", int64(123)},
   369  			{"version", "onetwothree"},
   370  		},
   371  		bson.D{
   372  			// Already migrated, with no settings.
   373  			{"_id", "5"},
   374  			{"model-uuid", "model-uuid"},
   375  			{"txn-revno", int64(99)},
   376  			{"txn-queue", []string{}},
   377  			{"version", int64(98)},
   378  			{"settings", map[string]interface{}{}},
   379  		},
   380  		bson.D{
   381  			// Already migrated, with settings.
   382  			{"_id", "6"},
   383  			{"model-uuid", "model-uuid"},
   384  			{"txn-revno", int64(99)},
   385  			{"txn-queue", []string{}},
   386  			{"version", int64(98)},
   387  			{"settings", bson.D{
   388  				{"settings", int64(123)},
   389  				{"version", "onetwothree"},
   390  			}},
   391  		},
   392  	)
   393  	c.Assert(err, jc.ErrorIsNil)
   394  
   395  	// Expected docs, excluding txn-queu which we cannot predict.
   396  	expected := []bson.M{{
   397  		"_id":        "1",
   398  		"model-uuid": "model-uuid",
   399  		"txn-revno":  int64(100),
   400  		"settings":   bson.M{},
   401  		"version":    int64(99),
   402  	}, {
   403  		"_id":        "2",
   404  		"model-uuid": "model-uuid",
   405  		"txn-revno":  int64(101),
   406  		"settings": bson.M{
   407  			"settings": int64(123),
   408  			"version":  "onetwothree",
   409  		},
   410  		"version": int64(99),
   411  	}, {
   412  		"_id":       "3",
   413  		"txn-revno": int64(100),
   414  		"settings":  bson.M{},
   415  		"version":   int64(99),
   416  	}, {
   417  		"_id":       "4",
   418  		"txn-revno": int64(101),
   419  		"settings": bson.M{
   420  			"settings": int64(123),
   421  			"version":  "onetwothree",
   422  		},
   423  		"version": int64(99),
   424  	}, {
   425  		"_id":        "5",
   426  		"model-uuid": "model-uuid",
   427  		"txn-revno":  int64(99),
   428  		"version":    int64(98),
   429  		"settings":   bson.M{},
   430  	}, {
   431  		"_id":        "6",
   432  		"model-uuid": "model-uuid",
   433  		"txn-revno":  int64(99),
   434  		"version":    int64(98),
   435  		"settings": bson.M{
   436  			"settings": int64(123),
   437  			"version":  "onetwothree",
   438  		},
   439  	}}
   440  
   441  	// Two rounds to check idempotency.
   442  	for i := 0; i < 2; i++ {
   443  		err = MigrateSettingsSchema(s.state)
   444  		c.Assert(err, jc.ErrorIsNil)
   445  
   446  		var docs []bson.M
   447  		err = settingsColl.Find(
   448  			bson.D{{"model-uuid", bson.D{{"$ne", s.state.ModelUUID()}}}},
   449  		).Sort("_id").Select(bson.M{"txn-queue": 0}).All(&docs)
   450  		c.Assert(err, jc.ErrorIsNil)
   451  		c.Assert(docs, jc.DeepEquals, expected)
   452  	}
   453  }
   454  
   455  func (s *upgradesSuite) setupAddDefaultEndpointBindingsToServices(c *gc.C) []*Application {
   456  	// Add an owner user.
   457  	stateOwner, err := s.state.AddUser("bob", "notused", "notused", "bob")
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	ownerTag := stateOwner.UserTag()
   460  	_, err = s.state.AddModelUser(s.state.ModelUUID(), UserAccessSpec{
   461  		User:        ownerTag,
   462  		CreatedBy:   ownerTag,
   463  		DisplayName: "",
   464  		Access:      permission.ReadAccess,
   465  	})
   466  	c.Assert(err, jc.ErrorIsNil)
   467  
   468  	// Add a couple of test spaces
   469  	_, err = s.state.AddSpace("db", "", nil, false)
   470  	c.Assert(err, jc.ErrorIsNil)
   471  	_, err = s.state.AddSpace("apps", "", nil, true)
   472  	c.Assert(err, jc.ErrorIsNil)
   473  
   474  	// Add some testing charms for the services.
   475  	charms := []*Charm{
   476  		AddTestingCharm(c, s.state, "wordpress"),
   477  		AddTestingCharm(c, s.state, "mysql"),
   478  	}
   479  
   480  	// Add a few services using the charms above: with no bindings, with just
   481  	// defaults, and with explicitly given bindings. For the first case we need
   482  	// to manually remove the added default bindings.
   483  	wpBindings := map[string]string{
   484  		"db":  "db",
   485  		"url": "apps",
   486  	}
   487  	msBindings := map[string]string{
   488  		"server": "db",
   489  	}
   490  	services := []*Application{
   491  		AddTestingService(c, s.state, "wp-no-bindings", charms[0]),
   492  		AddTestingService(c, s.state, "ms-no-bindings", charms[1]),
   493  
   494  		AddTestingService(c, s.state, "wp-default-bindings", charms[0]),
   495  		AddTestingService(c, s.state, "ms-default-bindings", charms[1]),
   496  
   497  		AddTestingServiceWithBindings(c, s.state, "wp-given-bindings", charms[0], wpBindings),
   498  		AddTestingServiceWithBindings(c, s.state, "ms-given-bindings", charms[1], msBindings),
   499  	}
   500  
   501  	// Drop the added endpoint bindings doc directly for the first two services.
   502  	ops := []txn.Op{
   503  		removeEndpointBindingsOp(services[0].globalKey()),
   504  		removeEndpointBindingsOp(services[1].globalKey()),
   505  	}
   506  	err = s.state.runTransaction(ops)
   507  	c.Assert(err, jc.ErrorIsNil)
   508  
   509  	return services
   510  }
   511  
   512  func (s *upgradesSuite) getServicesBindings(c *gc.C, services []*Application) map[string]map[string]string {
   513  	currentBindings := make(map[string]map[string]string, len(services))
   514  	for i := range services {
   515  		applicationname := services[i].Name()
   516  		serviceBindings, err := services[i].EndpointBindings()
   517  		if err != nil {
   518  			c.Fatalf("unexpected error getting service %q bindings: %v", applicationname, err)
   519  		}
   520  		currentBindings[applicationname] = serviceBindings
   521  	}
   522  	return currentBindings
   523  }
   524  
   525  func (s *upgradesSuite) testAddDefaultEndpointBindingsToServices(c *gc.C, runTwice bool) {
   526  	services := s.setupAddDefaultEndpointBindingsToServices(c)
   527  	initialBindings := s.getServicesBindings(c, services)
   528  	wpAllDefaults := map[string]string{
   529  		// relation names
   530  		"url":             "",
   531  		"logging-dir":     "",
   532  		"monitoring-port": "",
   533  		"db":              "",
   534  		"cache":           "",
   535  		// extra-bindings
   536  		"db-client": "",
   537  		"admin-api": "",
   538  		"foo-bar":   "",
   539  	}
   540  	msAllDefaults := map[string]string{
   541  		"server": "",
   542  	}
   543  	expectedInitialAndFinal := map[string]map[string]string{
   544  		"wp-no-bindings":      wpAllDefaults,
   545  		"wp-default-bindings": wpAllDefaults,
   546  		"wp-given-bindings": map[string]string{
   547  			"url":             "apps",
   548  			"logging-dir":     "",
   549  			"monitoring-port": "",
   550  			"db":              "db",
   551  			"cache":           "",
   552  			"db-client":       "",
   553  			"admin-api":       "",
   554  			"foo-bar":         "",
   555  		},
   556  
   557  		"ms-no-bindings":      msAllDefaults,
   558  		"ms-default-bindings": msAllDefaults,
   559  		"ms-given-bindings": map[string]string{
   560  			"server": "db",
   561  		},
   562  	}
   563  	c.Assert(initialBindings, jc.DeepEquals, expectedInitialAndFinal)
   564  
   565  	assertFinalBindings := func() {
   566  		finalBindings := s.getServicesBindings(c, services)
   567  		c.Assert(finalBindings, jc.DeepEquals, expectedInitialAndFinal)
   568  	}
   569  	err := AddDefaultEndpointBindingsToServices(s.state)
   570  	c.Assert(err, jc.ErrorIsNil)
   571  	assertFinalBindings()
   572  
   573  	if runTwice {
   574  		err = AddDefaultEndpointBindingsToServices(s.state)
   575  		c.Assert(err, jc.ErrorIsNil, gc.Commentf("idempotency check failed!"))
   576  		assertFinalBindings()
   577  	}
   578  }
   579  
   580  func (s *upgradesSuite) TestAddDefaultEndpointBindingsToServices(c *gc.C) {
   581  	s.testAddDefaultEndpointBindingsToServices(c, false)
   582  }
   583  
   584  func (s *upgradesSuite) TestAddDefaultEndpointBindingsToServicesIdempotent(c *gc.C) {
   585  	s.testAddDefaultEndpointBindingsToServices(c, true)
   586  }