github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/status"
    16  	"github.com/juju/juju/storage/provider"
    17  	"github.com/juju/juju/storage/provider/registry"
    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.IsNoAddress)
    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.IsNoAddress)
    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  	registry.RegisterEnvironStorageProviders("someprovider", provider.LoopProviderType, provider.RootfsProviderType)
   230  	// Make an unprovisioned machine with storage for tests to use.
   231  	// TODO(axw) extend testing/factory to allow creating unprovisioned
   232  	// machines.
   233  	m, err := st.AddOneMachine(MachineTemplate{
   234  		Series: "quantal",
   235  		Jobs:   []MachineJob{JobHostUnits},
   236  		Volumes: []MachineVolumeParams{
   237  			{Volume: VolumeParams{Pool: "loop", Size: 2048}},
   238  		},
   239  		Filesystems: []MachineFilesystemParams{
   240  			{Filesystem: FilesystemParams{Pool: "rootfs", Size: 2048}},
   241  		},
   242  	})
   243  	c.Assert(err, jc.ErrorIsNil)
   244  
   245  	va, err := m.VolumeAttachments()
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	c.Assert(va, gc.HasLen, 1)
   248  	v, err := st.Volume(va[0].Volume())
   249  	c.Assert(err, jc.ErrorIsNil)
   250  
   251  	fa, err := st.MachineFilesystemAttachments(m.MachineTag())
   252  	c.Assert(err, jc.ErrorIsNil)
   253  	c.Assert(fa, gc.HasLen, 1)
   254  	f, err := st.Filesystem(fa[0].Filesystem())
   255  	c.Assert(err, jc.ErrorIsNil)
   256  
   257  	return m, v, f, m.Destroy
   258  }
   259  
   260  func (s *upgradesSuite) TestAddFilesystemStatus(c *gc.C) {
   261  	_, _, filesystem, cleanup := setupMachineBoundStorageTests(c, s.state)
   262  	defer cleanup()
   263  
   264  	removeStatusDoc(c, s.state, filesystem)
   265  	_, err := filesystem.Status()
   266  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   267  	s.assertAddFilesystemStatus(c, filesystem, status.StatusPending)
   268  }
   269  
   270  func (s *upgradesSuite) TestAddFilesystemStatusDoesNotOverwrite(c *gc.C) {
   271  	_, _, filesystem, cleanup := setupMachineBoundStorageTests(c, s.state)
   272  	defer cleanup()
   273  
   274  	err := filesystem.SetStatus(status.StatusDestroying, "", nil)
   275  	c.Assert(err, jc.ErrorIsNil)
   276  	s.assertAddFilesystemStatus(c, filesystem, status.StatusDestroying)
   277  }
   278  
   279  func (s *upgradesSuite) TestAddFilesystemStatusProvisioned(c *gc.C) {
   280  	_, _, filesystem, cleanup := setupMachineBoundStorageTests(c, s.state)
   281  	defer cleanup()
   282  
   283  	err := s.state.SetFilesystemInfo(filesystem.FilesystemTag(), FilesystemInfo{
   284  		FilesystemId: "fs",
   285  	})
   286  	c.Assert(err, jc.ErrorIsNil)
   287  	removeStatusDoc(c, s.state, filesystem)
   288  	s.assertAddFilesystemStatus(c, filesystem, status.StatusAttaching)
   289  }
   290  
   291  func (s *upgradesSuite) TestAddFilesystemStatusAttached(c *gc.C) {
   292  	machine, _, filesystem, cleanup := setupMachineBoundStorageTests(c, s.state)
   293  	defer cleanup()
   294  
   295  	err := machine.SetProvisioned("fake", "fake", nil)
   296  	c.Assert(err, jc.ErrorIsNil)
   297  
   298  	err = s.state.SetFilesystemInfo(filesystem.FilesystemTag(), FilesystemInfo{
   299  		FilesystemId: "fs",
   300  	})
   301  	c.Assert(err, jc.ErrorIsNil)
   302  
   303  	err = s.state.SetFilesystemAttachmentInfo(
   304  		machine.MachineTag(),
   305  		filesystem.FilesystemTag(),
   306  		FilesystemAttachmentInfo{},
   307  	)
   308  	c.Assert(err, jc.ErrorIsNil)
   309  
   310  	removeStatusDoc(c, s.state, filesystem)
   311  	s.assertAddFilesystemStatus(c, filesystem, status.StatusAttached)
   312  }
   313  
   314  func (s *upgradesSuite) assertAddFilesystemStatus(c *gc.C, filesystem Filesystem, expect status.Status) {
   315  	err := AddFilesystemStatus(s.state)
   316  	c.Assert(err, jc.ErrorIsNil)
   317  
   318  	info, err := filesystem.Status()
   319  	c.Assert(err, jc.ErrorIsNil)
   320  	c.Assert(info.Status, gc.Equals, expect)
   321  }
   322  
   323  func removeStatusDoc(c *gc.C, st *State, g GlobalEntity) {
   324  	op := removeStatusOp(st, g.globalKey())
   325  	err := st.runTransaction([]txn.Op{op})
   326  	c.Assert(err, jc.ErrorIsNil)
   327  }
   328  
   329  func (s *upgradesSuite) TestMigrateSettingsSchema(c *gc.C) {
   330  	// Insert test documents.
   331  	settingsColl, closer := s.state.getRawCollection(settingsC)
   332  	defer closer()
   333  	err := settingsColl.Insert(
   334  		bson.D{
   335  			// Post-model-uuid migration, with no settings.
   336  			{"_id", "1"},
   337  			{"model-uuid", "model-uuid"},
   338  			{"txn-revno", int64(99)},
   339  			{"txn-queue", []string{}},
   340  		},
   341  		bson.D{
   342  			// Post-model-uuid migration, with settings. One
   343  			// of the settings is called "settings", and
   344  			// one "version".
   345  			{"_id", "2"},
   346  			{"model-uuid", "model-uuid"},
   347  			{"txn-revno", int64(99)},
   348  			{"txn-queue", []string{}},
   349  			{"settings", int64(123)},
   350  			{"version", "onetwothree"},
   351  		},
   352  		bson.D{
   353  			// Pre-model-uuid migration, with no settings.
   354  			{"_id", "3"},
   355  			{"txn-revno", int64(99)},
   356  			{"txn-queue", []string{}},
   357  		},
   358  		bson.D{
   359  			// Pre-model-uuid migration, with settings.
   360  			{"_id", "4"},
   361  			{"txn-revno", int64(99)},
   362  			{"txn-queue", []string{}},
   363  			{"settings", int64(123)},
   364  			{"version", "onetwothree"},
   365  		},
   366  		bson.D{
   367  			// Already migrated, with no settings.
   368  			{"_id", "5"},
   369  			{"model-uuid", "model-uuid"},
   370  			{"txn-revno", int64(99)},
   371  			{"txn-queue", []string{}},
   372  			{"version", int64(98)},
   373  			{"settings", map[string]interface{}{}},
   374  		},
   375  		bson.D{
   376  			// Already migrated, with settings.
   377  			{"_id", "6"},
   378  			{"model-uuid", "model-uuid"},
   379  			{"txn-revno", int64(99)},
   380  			{"txn-queue", []string{}},
   381  			{"version", int64(98)},
   382  			{"settings", bson.D{
   383  				{"settings", int64(123)},
   384  				{"version", "onetwothree"},
   385  			}},
   386  		},
   387  	)
   388  	c.Assert(err, jc.ErrorIsNil)
   389  
   390  	// Expected docs, excluding txn-queu which we cannot predict.
   391  	expected := []bson.M{{
   392  		"_id":        "1",
   393  		"model-uuid": "model-uuid",
   394  		"txn-revno":  int64(100),
   395  		"settings":   bson.M{},
   396  		"version":    int64(99),
   397  	}, {
   398  		"_id":        "2",
   399  		"model-uuid": "model-uuid",
   400  		"txn-revno":  int64(101),
   401  		"settings": bson.M{
   402  			"settings": int64(123),
   403  			"version":  "onetwothree",
   404  		},
   405  		"version": int64(99),
   406  	}, {
   407  		"_id":       "3",
   408  		"txn-revno": int64(100),
   409  		"settings":  bson.M{},
   410  		"version":   int64(99),
   411  	}, {
   412  		"_id":       "4",
   413  		"txn-revno": int64(101),
   414  		"settings": bson.M{
   415  			"settings": int64(123),
   416  			"version":  "onetwothree",
   417  		},
   418  		"version": int64(99),
   419  	}, {
   420  		"_id":        "5",
   421  		"model-uuid": "model-uuid",
   422  		"txn-revno":  int64(99),
   423  		"version":    int64(98),
   424  		"settings":   bson.M{},
   425  	}, {
   426  		"_id":        "6",
   427  		"model-uuid": "model-uuid",
   428  		"txn-revno":  int64(99),
   429  		"version":    int64(98),
   430  		"settings": bson.M{
   431  			"settings": int64(123),
   432  			"version":  "onetwothree",
   433  		},
   434  	}}
   435  
   436  	// Two rounds to check idempotency.
   437  	for i := 0; i < 2; i++ {
   438  		err = MigrateSettingsSchema(s.state)
   439  		c.Assert(err, jc.ErrorIsNil)
   440  
   441  		var docs []bson.M
   442  		err = settingsColl.Find(
   443  			bson.D{{"model-uuid", bson.D{{"$ne", s.state.ModelUUID()}}}},
   444  		).Sort("_id").Select(bson.M{"txn-queue": 0}).All(&docs)
   445  		c.Assert(err, jc.ErrorIsNil)
   446  		c.Assert(docs, jc.DeepEquals, expected)
   447  	}
   448  }
   449  
   450  func (s *upgradesSuite) setupAddDefaultEndpointBindingsToServices(c *gc.C) []*Service {
   451  	// Add an owner user.
   452  	stateOwner, err := s.state.AddUser("bob", "notused", "notused", "bob")
   453  	c.Assert(err, jc.ErrorIsNil)
   454  	ownerTag := stateOwner.UserTag()
   455  	_, err = s.state.AddModelUser(ModelUserSpec{
   456  		User:        ownerTag,
   457  		CreatedBy:   ownerTag,
   458  		DisplayName: "",
   459  	})
   460  	c.Assert(err, jc.ErrorIsNil)
   461  
   462  	// Add a couple of test spaces
   463  	_, err = s.state.AddSpace("db", "", nil, false)
   464  	c.Assert(err, jc.ErrorIsNil)
   465  	_, err = s.state.AddSpace("apps", "", nil, true)
   466  	c.Assert(err, jc.ErrorIsNil)
   467  
   468  	// Add some testing charms for the services.
   469  	charms := []*Charm{
   470  		AddTestingCharm(c, s.state, "wordpress"),
   471  		AddTestingCharm(c, s.state, "mysql"),
   472  	}
   473  
   474  	// Add a few services using the charms above: with no bindings, with just
   475  	// defaults, and with explicitly given bindings. For the first case we need
   476  	// to manually remove the added default bindings.
   477  	wpBindings := map[string]string{
   478  		"db":  "db",
   479  		"url": "apps",
   480  	}
   481  	msBindings := map[string]string{
   482  		"server": "db",
   483  	}
   484  	services := []*Service{
   485  		AddTestingService(c, s.state, "wp-no-bindings", charms[0], ownerTag),
   486  		AddTestingService(c, s.state, "ms-no-bindings", charms[1], ownerTag),
   487  
   488  		AddTestingService(c, s.state, "wp-default-bindings", charms[0], ownerTag),
   489  		AddTestingService(c, s.state, "ms-default-bindings", charms[1], ownerTag),
   490  
   491  		AddTestingServiceWithBindings(c, s.state, "wp-given-bindings", charms[0], ownerTag, wpBindings),
   492  		AddTestingServiceWithBindings(c, s.state, "ms-given-bindings", charms[1], ownerTag, msBindings),
   493  	}
   494  
   495  	// Drop the added endpoint bindings doc directly for the first two services.
   496  	ops := []txn.Op{
   497  		removeEndpointBindingsOp(services[0].globalKey()),
   498  		removeEndpointBindingsOp(services[1].globalKey()),
   499  	}
   500  	err = s.state.runTransaction(ops)
   501  	c.Assert(err, jc.ErrorIsNil)
   502  
   503  	return services
   504  }
   505  
   506  func (s *upgradesSuite) getServicesBindings(c *gc.C, services []*Service) map[string]map[string]string {
   507  	currentBindings := make(map[string]map[string]string, len(services))
   508  	for i := range services {
   509  		serviceName := services[i].Name()
   510  		serviceBindings, err := services[i].EndpointBindings()
   511  		if err != nil {
   512  			c.Fatalf("unexpected error getting service %q bindings: %v", serviceName, err)
   513  		}
   514  		currentBindings[serviceName] = serviceBindings
   515  	}
   516  	return currentBindings
   517  }
   518  
   519  func (s *upgradesSuite) testAddDefaultEndpointBindingsToServices(c *gc.C, runTwice bool) {
   520  	services := s.setupAddDefaultEndpointBindingsToServices(c)
   521  	initialBindings := s.getServicesBindings(c, services)
   522  	wpAllDefaults := map[string]string{
   523  		// relation names
   524  		"url":             "",
   525  		"logging-dir":     "",
   526  		"monitoring-port": "",
   527  		"db":              "",
   528  		"cache":           "",
   529  		// extra-bindings
   530  		"db-client": "",
   531  		"admin-api": "",
   532  		"foo-bar":   "",
   533  	}
   534  	msAllDefaults := map[string]string{
   535  		"server": "",
   536  	}
   537  	expectedInitialAndFinal := map[string]map[string]string{
   538  		"wp-no-bindings":      wpAllDefaults,
   539  		"wp-default-bindings": wpAllDefaults,
   540  		"wp-given-bindings": map[string]string{
   541  			"url":             "apps",
   542  			"logging-dir":     "",
   543  			"monitoring-port": "",
   544  			"db":              "db",
   545  			"cache":           "",
   546  			"db-client":       "",
   547  			"admin-api":       "",
   548  			"foo-bar":         "",
   549  		},
   550  
   551  		"ms-no-bindings":      msAllDefaults,
   552  		"ms-default-bindings": msAllDefaults,
   553  		"ms-given-bindings": map[string]string{
   554  			"server": "db",
   555  		},
   556  	}
   557  	c.Assert(initialBindings, jc.DeepEquals, expectedInitialAndFinal)
   558  
   559  	assertFinalBindings := func() {
   560  		finalBindings := s.getServicesBindings(c, services)
   561  		c.Assert(finalBindings, jc.DeepEquals, expectedInitialAndFinal)
   562  	}
   563  	err := AddDefaultEndpointBindingsToServices(s.state)
   564  	c.Assert(err, jc.ErrorIsNil)
   565  	assertFinalBindings()
   566  
   567  	if runTwice {
   568  		err = AddDefaultEndpointBindingsToServices(s.state)
   569  		c.Assert(err, jc.ErrorIsNil, gc.Commentf("idempotency check failed!"))
   570  		assertFinalBindings()
   571  	}
   572  }
   573  
   574  func (s *upgradesSuite) TestAddDefaultEndpointBindingsToServices(c *gc.C) {
   575  	s.testAddDefaultEndpointBindingsToServices(c, false)
   576  }
   577  
   578  func (s *upgradesSuite) TestAddDefaultEndpointBindingsToServicesIdempotent(c *gc.C) {
   579  	s.testAddDefaultEndpointBindingsToServices(c, true)
   580  }