github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/service_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	jc "github.com/juju/testing/checkers"
    13  	"labix.org/v2/mgo"
    14  	gc "launchpad.net/gocheck"
    15  
    16  	"github.com/juju/juju/charm"
    17  	"github.com/juju/juju/constraints"
    18  	"github.com/juju/juju/environs/config"
    19  	"github.com/juju/juju/state"
    20  	"github.com/juju/juju/state/testing"
    21  )
    22  
    23  type ServiceSuite struct {
    24  	ConnSuite
    25  	charm *state.Charm
    26  	mysql *state.Service
    27  }
    28  
    29  var _ = gc.Suite(&ServiceSuite{})
    30  
    31  func (s *ServiceSuite) SetUpTest(c *gc.C) {
    32  	s.ConnSuite.SetUpTest(c)
    33  	s.policy.getConstraintsValidator = func(*config.Config) (constraints.Validator, error) {
    34  		validator := constraints.NewValidator()
    35  		validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem})
    36  		validator.RegisterUnsupported([]string{constraints.CpuPower})
    37  		return validator, nil
    38  	}
    39  	s.charm = s.AddTestingCharm(c, "mysql")
    40  	s.mysql = s.AddTestingService(c, "mysql", s.charm)
    41  }
    42  
    43  func (s *ServiceSuite) TestSetCharm(c *gc.C) {
    44  	ch, force, err := s.mysql.Charm()
    45  	c.Assert(err, gc.IsNil)
    46  	c.Assert(ch.URL(), gc.DeepEquals, s.charm.URL())
    47  	c.Assert(force, gc.Equals, false)
    48  	url, force := s.mysql.CharmURL()
    49  	c.Assert(url, gc.DeepEquals, s.charm.URL())
    50  	c.Assert(force, gc.Equals, false)
    51  
    52  	// Add a compatible charm and force it.
    53  	sch := s.AddMetaCharm(c, "mysql", metaBase, 2) // revno 1 is used by SetUpSuite
    54  	err = s.mysql.SetCharm(sch, true)
    55  	c.Assert(err, gc.IsNil)
    56  	ch, force, err = s.mysql.Charm()
    57  	c.Assert(err, gc.IsNil)
    58  	c.Assert(ch.URL(), gc.DeepEquals, sch.URL())
    59  	c.Assert(force, gc.Equals, true)
    60  	url, force = s.mysql.CharmURL()
    61  	c.Assert(url, gc.DeepEquals, sch.URL())
    62  	c.Assert(force, gc.Equals, true)
    63  
    64  	// SetCharm fails when the service is Dying.
    65  	_, err = s.mysql.AddUnit()
    66  	c.Assert(err, gc.IsNil)
    67  	err = s.mysql.Destroy()
    68  	c.Assert(err, gc.IsNil)
    69  	err = s.mysql.SetCharm(sch, true)
    70  	c.Assert(err, gc.ErrorMatches, `service "mysql" is not alive`)
    71  }
    72  
    73  func (s *ServiceSuite) TestSetCharmErrors(c *gc.C) {
    74  	logging := s.AddTestingCharm(c, "logging")
    75  	err := s.mysql.SetCharm(logging, false)
    76  	c.Assert(err, gc.ErrorMatches, "cannot change a service's subordinacy")
    77  
    78  	othermysql := s.AddSeriesCharm(c, "mysql", "otherseries")
    79  	err = s.mysql.SetCharm(othermysql, false)
    80  	c.Assert(err, gc.ErrorMatches, "cannot change a service's series")
    81  }
    82  
    83  var metaBase = `
    84  name: mysql
    85  summary: "Fake MySQL Database engine"
    86  description: "Complete with nonsense relations"
    87  provides:
    88    server: mysql
    89  requires:
    90    client: mysql
    91  peers:
    92    cluster: mysql
    93  `
    94  var metaDifferentProvider = `
    95  name: mysql
    96  description: none
    97  summary: none
    98  provides:
    99    kludge: mysql
   100  requires:
   101    client: mysql
   102  peers:
   103    cluster: mysql
   104  `
   105  var metaDifferentRequirer = `
   106  name: mysql
   107  description: none
   108  summary: none
   109  provides:
   110    server: mysql
   111  requires:
   112    kludge: mysql
   113  peers:
   114    cluster: mysql
   115  `
   116  var metaDifferentPeer = `
   117  name: mysql
   118  description: none
   119  summary: none
   120  provides:
   121    server: mysql
   122  requires:
   123    client: mysql
   124  peers:
   125    kludge: mysql
   126  `
   127  var metaExtraEndpoints = `
   128  name: mysql
   129  description: none
   130  summary: none
   131  provides:
   132    server: mysql
   133    foo: bar
   134  requires:
   135    client: mysql
   136    baz: woot
   137  peers:
   138    cluster: mysql
   139    just: me
   140  `
   141  
   142  var setCharmEndpointsTests = []struct {
   143  	summary string
   144  	meta    string
   145  	err     string
   146  }{
   147  	{
   148  		summary: "different provider (but no relation yet)",
   149  		meta:    metaDifferentProvider,
   150  	}, {
   151  		summary: "different requirer (but no relation yet)",
   152  		meta:    metaDifferentRequirer,
   153  	}, {
   154  		summary: "different peer",
   155  		meta:    metaDifferentPeer,
   156  		err:     `cannot upgrade service "fakemysql" to charm "local:quantal/quantal-mysql-5": would break relation "fakemysql:cluster"`,
   157  	}, {
   158  		summary: "same relations ok",
   159  		meta:    metaBase,
   160  	}, {
   161  		summary: "extra endpoints ok",
   162  		meta:    metaExtraEndpoints,
   163  	},
   164  }
   165  
   166  func (s *ServiceSuite) TestSetCharmChecksEndpointsWithoutRelations(c *gc.C) {
   167  	revno := 2 // 1 is used in SetUpSuite
   168  	ms := s.AddMetaCharm(c, "mysql", metaBase, revno)
   169  	svc := s.AddTestingService(c, "fakemysql", ms)
   170  	err := svc.SetCharm(ms, false)
   171  	c.Assert(err, gc.IsNil)
   172  
   173  	for i, t := range setCharmEndpointsTests {
   174  		c.Logf("test %d: %s", i, t.summary)
   175  
   176  		newCh := s.AddMetaCharm(c, "mysql", t.meta, revno+i+1)
   177  		err = svc.SetCharm(newCh, false)
   178  		if t.err != "" {
   179  			c.Assert(err, gc.ErrorMatches, t.err)
   180  		} else {
   181  			c.Assert(err, gc.IsNil)
   182  		}
   183  	}
   184  
   185  	err = svc.Destroy()
   186  	c.Assert(err, gc.IsNil)
   187  }
   188  
   189  func (s *ServiceSuite) TestSetCharmChecksEndpointsWithRelations(c *gc.C) {
   190  	revno := 2 // 1 is used by SetUpSuite
   191  	providerCharm := s.AddMetaCharm(c, "mysql", metaDifferentProvider, revno)
   192  	providerSvc := s.AddTestingService(c, "myprovider", providerCharm)
   193  	err := providerSvc.SetCharm(providerCharm, false)
   194  	c.Assert(err, gc.IsNil)
   195  
   196  	revno++
   197  	requirerCharm := s.AddMetaCharm(c, "mysql", metaDifferentRequirer, revno)
   198  	requirerSvc := s.AddTestingService(c, "myrequirer", requirerCharm)
   199  	err = requirerSvc.SetCharm(requirerCharm, false)
   200  	c.Assert(err, gc.IsNil)
   201  
   202  	eps, err := s.State.InferEndpoints([]string{"myprovider:kludge", "myrequirer:kludge"})
   203  	c.Assert(err, gc.IsNil)
   204  	_, err = s.State.AddRelation(eps...)
   205  	c.Assert(err, gc.IsNil)
   206  
   207  	revno++
   208  	baseCharm := s.AddMetaCharm(c, "mysql", metaBase, revno)
   209  	err = providerSvc.SetCharm(baseCharm, false)
   210  	c.Assert(err, gc.ErrorMatches, `cannot upgrade service "myprovider" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`)
   211  	err = requirerSvc.SetCharm(baseCharm, false)
   212  	c.Assert(err, gc.ErrorMatches, `cannot upgrade service "myrequirer" to charm "local:quantal/quantal-mysql-4": would break relation "myrequirer:kludge myprovider:kludge"`)
   213  }
   214  
   215  var stringConfig = `
   216  options:
   217    key: {default: My Key, description: Desc, type: string}
   218  `
   219  var emptyConfig = `
   220  options: {}
   221  `
   222  var floatConfig = `
   223  options:
   224    key: {default: 0.42, description: Float key, type: float}
   225  `
   226  var newStringConfig = `
   227  options:
   228    key: {default: My Key, description: Desc, type: string}
   229    other: {default: None, description: My Other, type: string}
   230  `
   231  
   232  var setCharmConfigTests = []struct {
   233  	summary     string
   234  	startconfig string
   235  	startvalues charm.Settings
   236  	endconfig   string
   237  	endvalues   charm.Settings
   238  	err         string
   239  }{
   240  	{
   241  		summary:     "add float key to empty config",
   242  		startconfig: emptyConfig,
   243  		endconfig:   floatConfig,
   244  	}, {
   245  		summary:     "add string key to empty config",
   246  		startconfig: emptyConfig,
   247  		endconfig:   stringConfig,
   248  	}, {
   249  		summary:     "add string key and preserve existing values",
   250  		startconfig: stringConfig,
   251  		startvalues: charm.Settings{"key": "foo"},
   252  		endconfig:   newStringConfig,
   253  		endvalues:   charm.Settings{"key": "foo"},
   254  	}, {
   255  		summary:     "remove string key",
   256  		startconfig: stringConfig,
   257  		startvalues: charm.Settings{"key": "value"},
   258  		endconfig:   emptyConfig,
   259  	}, {
   260  		summary:     "remove float key",
   261  		startconfig: floatConfig,
   262  		startvalues: charm.Settings{"key": 123.45},
   263  		endconfig:   emptyConfig,
   264  	}, {
   265  		summary:     "change key type without values",
   266  		startconfig: stringConfig,
   267  		endconfig:   floatConfig,
   268  	}, {
   269  		summary:     "change key type with values",
   270  		startconfig: stringConfig,
   271  		startvalues: charm.Settings{"key": "value"},
   272  		endconfig:   floatConfig,
   273  	},
   274  }
   275  
   276  func (s *ServiceSuite) TestSetCharmConfig(c *gc.C) {
   277  	charms := map[string]*state.Charm{
   278  		stringConfig:    s.AddConfigCharm(c, "wordpress", stringConfig, 1),
   279  		emptyConfig:     s.AddConfigCharm(c, "wordpress", emptyConfig, 2),
   280  		floatConfig:     s.AddConfigCharm(c, "wordpress", floatConfig, 3),
   281  		newStringConfig: s.AddConfigCharm(c, "wordpress", newStringConfig, 4),
   282  	}
   283  
   284  	for i, t := range setCharmConfigTests {
   285  		c.Logf("test %d: %s", i, t.summary)
   286  
   287  		origCh := charms[t.startconfig]
   288  		svc := s.AddTestingService(c, "wordpress", origCh)
   289  		err := svc.UpdateConfigSettings(t.startvalues)
   290  		c.Assert(err, gc.IsNil)
   291  
   292  		newCh := charms[t.endconfig]
   293  		err = svc.SetCharm(newCh, false)
   294  		var expectVals charm.Settings
   295  		var expectCh *state.Charm
   296  		if t.err != "" {
   297  			c.Assert(err, gc.ErrorMatches, t.err)
   298  			expectCh = origCh
   299  			expectVals = t.startvalues
   300  		} else {
   301  			c.Assert(err, gc.IsNil)
   302  			expectCh = newCh
   303  			expectVals = t.endvalues
   304  		}
   305  
   306  		sch, _, err := svc.Charm()
   307  		c.Assert(err, gc.IsNil)
   308  		c.Assert(sch.URL(), gc.DeepEquals, expectCh.URL())
   309  		settings, err := svc.ConfigSettings()
   310  		c.Assert(err, gc.IsNil)
   311  		if len(expectVals) == 0 {
   312  			c.Assert(settings, gc.HasLen, 0)
   313  		} else {
   314  			c.Assert(settings, gc.DeepEquals, expectVals)
   315  		}
   316  
   317  		err = svc.Destroy()
   318  		c.Assert(err, gc.IsNil)
   319  	}
   320  }
   321  
   322  var serviceUpdateConfigSettingsTests = []struct {
   323  	about   string
   324  	initial charm.Settings
   325  	update  charm.Settings
   326  	expect  charm.Settings
   327  	err     string
   328  }{{
   329  	about:  "unknown option",
   330  	update: charm.Settings{"foo": "bar"},
   331  	err:    `unknown option "foo"`,
   332  }, {
   333  	about:  "bad type",
   334  	update: charm.Settings{"skill-level": "profound"},
   335  	err:    `option "skill-level" expected int, got "profound"`,
   336  }, {
   337  	about:  "set string",
   338  	update: charm.Settings{"outlook": "positive"},
   339  	expect: charm.Settings{"outlook": "positive"},
   340  }, {
   341  	about:   "unset string and set another",
   342  	initial: charm.Settings{"outlook": "positive"},
   343  	update:  charm.Settings{"outlook": nil, "title": "sir"},
   344  	expect:  charm.Settings{"title": "sir"},
   345  }, {
   346  	about:  "unset missing string",
   347  	update: charm.Settings{"outlook": nil},
   348  }, {
   349  	about:   `empty strings are valid`,
   350  	initial: charm.Settings{"outlook": "positive"},
   351  	update:  charm.Settings{"outlook": "", "title": ""},
   352  	expect:  charm.Settings{"outlook": "", "title": ""},
   353  }, {
   354  	about:   "preserve existing value",
   355  	initial: charm.Settings{"title": "sir"},
   356  	update:  charm.Settings{"username": "admin001"},
   357  	expect:  charm.Settings{"username": "admin001", "title": "sir"},
   358  }, {
   359  	about:   "unset a default value, set a different default",
   360  	initial: charm.Settings{"username": "admin001", "title": "sir"},
   361  	update:  charm.Settings{"username": nil, "title": "My Title"},
   362  	expect:  charm.Settings{"title": "My Title"},
   363  }, {
   364  	about:  "non-string type",
   365  	update: charm.Settings{"skill-level": 303},
   366  	expect: charm.Settings{"skill-level": int64(303)},
   367  }, {
   368  	about:   "unset non-string type",
   369  	initial: charm.Settings{"skill-level": 303},
   370  	update:  charm.Settings{"skill-level": nil},
   371  }}
   372  
   373  func (s *ServiceSuite) TestUpdateConfigSettings(c *gc.C) {
   374  	sch := s.AddTestingCharm(c, "dummy")
   375  	for i, t := range serviceUpdateConfigSettingsTests {
   376  		c.Logf("test %d. %s", i, t.about)
   377  		svc := s.AddTestingService(c, "dummy-service", sch)
   378  		if t.initial != nil {
   379  			err := svc.UpdateConfigSettings(t.initial)
   380  			c.Assert(err, gc.IsNil)
   381  		}
   382  		err := svc.UpdateConfigSettings(t.update)
   383  		if t.err != "" {
   384  			c.Assert(err, gc.ErrorMatches, t.err)
   385  		} else {
   386  			c.Assert(err, gc.IsNil)
   387  			settings, err := svc.ConfigSettings()
   388  			c.Assert(err, gc.IsNil)
   389  			expect := t.expect
   390  			if expect == nil {
   391  				expect = charm.Settings{}
   392  			}
   393  			c.Assert(settings, gc.DeepEquals, expect)
   394  		}
   395  		err = svc.Destroy()
   396  		c.Assert(err, gc.IsNil)
   397  	}
   398  }
   399  
   400  func (s *ServiceSuite) TestSettingsRefCountWorks(c *gc.C) {
   401  	oldCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 1)
   402  	newCh := s.AddConfigCharm(c, "wordpress", emptyConfig, 2)
   403  	svcName := "mywp"
   404  
   405  	assertNoRef := func(sch *state.Charm) {
   406  		_, err := state.ServiceSettingsRefCount(s.State, svcName, sch.URL())
   407  		c.Assert(err, gc.Equals, mgo.ErrNotFound)
   408  	}
   409  	assertRef := func(sch *state.Charm, refcount int) {
   410  		rc, err := state.ServiceSettingsRefCount(s.State, svcName, sch.URL())
   411  		c.Assert(err, gc.IsNil)
   412  		c.Assert(rc, gc.Equals, refcount)
   413  	}
   414  
   415  	assertNoRef(oldCh)
   416  	assertNoRef(newCh)
   417  
   418  	svc := s.AddTestingService(c, svcName, oldCh)
   419  	assertRef(oldCh, 1)
   420  	assertNoRef(newCh)
   421  
   422  	err := svc.SetCharm(oldCh, false)
   423  	c.Assert(err, gc.IsNil)
   424  	assertRef(oldCh, 1)
   425  	assertNoRef(newCh)
   426  
   427  	err = svc.SetCharm(newCh, false)
   428  	c.Assert(err, gc.IsNil)
   429  	assertNoRef(oldCh)
   430  	assertRef(newCh, 1)
   431  
   432  	err = svc.SetCharm(oldCh, false)
   433  	c.Assert(err, gc.IsNil)
   434  	assertRef(oldCh, 1)
   435  	assertNoRef(newCh)
   436  
   437  	u, err := svc.AddUnit()
   438  	c.Assert(err, gc.IsNil)
   439  	curl, ok := u.CharmURL()
   440  	c.Assert(ok, gc.Equals, false)
   441  	assertRef(oldCh, 1)
   442  	assertNoRef(newCh)
   443  
   444  	err = u.SetCharmURL(oldCh.URL())
   445  	c.Assert(err, gc.IsNil)
   446  	curl, ok = u.CharmURL()
   447  	c.Assert(ok, gc.Equals, true)
   448  	c.Assert(curl, gc.DeepEquals, oldCh.URL())
   449  	assertRef(oldCh, 2)
   450  	assertNoRef(newCh)
   451  
   452  	err = u.EnsureDead()
   453  	c.Assert(err, gc.IsNil)
   454  	assertRef(oldCh, 2)
   455  	assertNoRef(newCh)
   456  
   457  	err = u.Remove()
   458  	c.Assert(err, gc.IsNil)
   459  	assertRef(oldCh, 1)
   460  	assertNoRef(newCh)
   461  
   462  	err = svc.Destroy()
   463  	c.Assert(err, gc.IsNil)
   464  	assertNoRef(oldCh)
   465  	assertNoRef(newCh)
   466  }
   467  
   468  const mysqlBaseMeta = `
   469  name: mysql
   470  summary: "Database engine"
   471  description: "A pretty popular database"
   472  provides:
   473    server: mysql
   474  `
   475  const onePeerMeta = `
   476  peers:
   477    cluster: mysql
   478  `
   479  const twoPeersMeta = `
   480  peers:
   481    cluster: mysql
   482    loadbalancer: phony
   483  `
   484  
   485  func (s *ServiceSuite) assertServiceRelations(c *gc.C, svc *state.Service, expectedKeys ...string) []*state.Relation {
   486  	rels, err := svc.Relations()
   487  	c.Assert(err, gc.IsNil)
   488  	if len(rels) == 0 {
   489  		return nil
   490  	}
   491  	relKeys := make([]string, len(expectedKeys))
   492  	for i, rel := range rels {
   493  		relKeys[i] = rel.String()
   494  	}
   495  	sort.Strings(relKeys)
   496  	c.Assert(relKeys, gc.DeepEquals, expectedKeys)
   497  	return rels
   498  }
   499  
   500  func (s *ServiceSuite) TestNewPeerRelationsAddedOnUpgrade(c *gc.C) {
   501  	// Original mysql charm has no peer relations.
   502  	oldCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+onePeerMeta, 2)
   503  	newCh := s.AddMetaCharm(c, "mysql", mysqlBaseMeta+twoPeersMeta, 3)
   504  
   505  	// No relations joined yet.
   506  	s.assertServiceRelations(c, s.mysql)
   507  
   508  	err := s.mysql.SetCharm(oldCh, false)
   509  	c.Assert(err, gc.IsNil)
   510  	s.assertServiceRelations(c, s.mysql, "mysql:cluster")
   511  
   512  	err = s.mysql.SetCharm(newCh, false)
   513  	c.Assert(err, gc.IsNil)
   514  	rels := s.assertServiceRelations(c, s.mysql, "mysql:cluster", "mysql:loadbalancer")
   515  
   516  	// Check state consistency by attempting to destroy the service.
   517  	err = s.mysql.Destroy()
   518  	c.Assert(err, gc.IsNil)
   519  
   520  	// Check the peer relations got destroyed as well.
   521  	for _, rel := range rels {
   522  		err = rel.Refresh()
   523  		c.Assert(err, jc.Satisfies, errors.IsNotFound)
   524  	}
   525  }
   526  
   527  func jujuInfoEp(serviceName string) state.Endpoint {
   528  	return state.Endpoint{
   529  		ServiceName: serviceName,
   530  		Relation: charm.Relation{
   531  			Interface: "juju-info",
   532  			Name:      "juju-info",
   533  			Role:      charm.RoleProvider,
   534  			Scope:     charm.ScopeGlobal,
   535  		},
   536  	}
   537  }
   538  
   539  func (s *ServiceSuite) TestTag(c *gc.C) {
   540  	c.Assert(s.mysql.Tag(), gc.Equals, "service-mysql")
   541  }
   542  
   543  func (s *ServiceSuite) TestMysqlEndpoints(c *gc.C) {
   544  	_, err := s.mysql.Endpoint("mysql")
   545  	c.Assert(err, gc.ErrorMatches, `service "mysql" has no "mysql" relation`)
   546  
   547  	jiEP, err := s.mysql.Endpoint("juju-info")
   548  	c.Assert(err, gc.IsNil)
   549  	c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("mysql"))
   550  
   551  	serverEP, err := s.mysql.Endpoint("server")
   552  	c.Assert(err, gc.IsNil)
   553  	c.Assert(serverEP, gc.DeepEquals, state.Endpoint{
   554  		ServiceName: "mysql",
   555  		Relation: charm.Relation{
   556  			Interface: "mysql",
   557  			Name:      "server",
   558  			Role:      charm.RoleProvider,
   559  			Scope:     charm.ScopeGlobal,
   560  		},
   561  	})
   562  
   563  	eps, err := s.mysql.Endpoints()
   564  	c.Assert(err, gc.IsNil)
   565  	c.Assert(eps, gc.DeepEquals, []state.Endpoint{jiEP, serverEP})
   566  }
   567  
   568  func (s *ServiceSuite) TestRiakEndpoints(c *gc.C) {
   569  	riak := s.AddTestingService(c, "myriak", s.AddTestingCharm(c, "riak"))
   570  
   571  	_, err := riak.Endpoint("garble")
   572  	c.Assert(err, gc.ErrorMatches, `service "myriak" has no "garble" relation`)
   573  
   574  	jiEP, err := riak.Endpoint("juju-info")
   575  	c.Assert(err, gc.IsNil)
   576  	c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("myriak"))
   577  
   578  	ringEP, err := riak.Endpoint("ring")
   579  	c.Assert(err, gc.IsNil)
   580  	c.Assert(ringEP, gc.DeepEquals, state.Endpoint{
   581  		ServiceName: "myriak",
   582  		Relation: charm.Relation{
   583  			Interface: "riak",
   584  			Name:      "ring",
   585  			Role:      charm.RolePeer,
   586  			Scope:     charm.ScopeGlobal,
   587  			Limit:     1,
   588  		},
   589  	})
   590  
   591  	adminEP, err := riak.Endpoint(state.AdminUser)
   592  	c.Assert(err, gc.IsNil)
   593  	c.Assert(adminEP, gc.DeepEquals, state.Endpoint{
   594  		ServiceName: "myriak",
   595  		Relation: charm.Relation{
   596  			Interface: "http",
   597  			Name:      state.AdminUser,
   598  			Role:      charm.RoleProvider,
   599  			Scope:     charm.ScopeGlobal,
   600  		},
   601  	})
   602  
   603  	endpointEP, err := riak.Endpoint("endpoint")
   604  	c.Assert(err, gc.IsNil)
   605  	c.Assert(endpointEP, gc.DeepEquals, state.Endpoint{
   606  		ServiceName: "myriak",
   607  		Relation: charm.Relation{
   608  			Interface: "http",
   609  			Name:      "endpoint",
   610  			Role:      charm.RoleProvider,
   611  			Scope:     charm.ScopeGlobal,
   612  		},
   613  	})
   614  
   615  	eps, err := riak.Endpoints()
   616  	c.Assert(err, gc.IsNil)
   617  	c.Assert(eps, gc.DeepEquals, []state.Endpoint{adminEP, endpointEP, jiEP, ringEP})
   618  }
   619  
   620  func (s *ServiceSuite) TestWordpressEndpoints(c *gc.C) {
   621  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   622  
   623  	_, err := wordpress.Endpoint("nonsense")
   624  	c.Assert(err, gc.ErrorMatches, `service "wordpress" has no "nonsense" relation`)
   625  
   626  	jiEP, err := wordpress.Endpoint("juju-info")
   627  	c.Assert(err, gc.IsNil)
   628  	c.Assert(jiEP, gc.DeepEquals, jujuInfoEp("wordpress"))
   629  
   630  	urlEP, err := wordpress.Endpoint("url")
   631  	c.Assert(err, gc.IsNil)
   632  	c.Assert(urlEP, gc.DeepEquals, state.Endpoint{
   633  		ServiceName: "wordpress",
   634  		Relation: charm.Relation{
   635  			Interface: "http",
   636  			Name:      "url",
   637  			Role:      charm.RoleProvider,
   638  			Scope:     charm.ScopeGlobal,
   639  		},
   640  	})
   641  
   642  	ldEP, err := wordpress.Endpoint("logging-dir")
   643  	c.Assert(err, gc.IsNil)
   644  	c.Assert(ldEP, gc.DeepEquals, state.Endpoint{
   645  		ServiceName: "wordpress",
   646  		Relation: charm.Relation{
   647  			Interface: "logging",
   648  			Name:      "logging-dir",
   649  			Role:      charm.RoleProvider,
   650  			Scope:     charm.ScopeContainer,
   651  		},
   652  	})
   653  
   654  	mpEP, err := wordpress.Endpoint("monitoring-port")
   655  	c.Assert(err, gc.IsNil)
   656  	c.Assert(mpEP, gc.DeepEquals, state.Endpoint{
   657  		ServiceName: "wordpress",
   658  		Relation: charm.Relation{
   659  			Interface: "monitoring",
   660  			Name:      "monitoring-port",
   661  			Role:      charm.RoleProvider,
   662  			Scope:     charm.ScopeContainer,
   663  		},
   664  	})
   665  
   666  	dbEP, err := wordpress.Endpoint("db")
   667  	c.Assert(err, gc.IsNil)
   668  	c.Assert(dbEP, gc.DeepEquals, state.Endpoint{
   669  		ServiceName: "wordpress",
   670  		Relation: charm.Relation{
   671  			Interface: "mysql",
   672  			Name:      "db",
   673  			Role:      charm.RoleRequirer,
   674  			Scope:     charm.ScopeGlobal,
   675  			Limit:     1,
   676  		},
   677  	})
   678  
   679  	cacheEP, err := wordpress.Endpoint("cache")
   680  	c.Assert(err, gc.IsNil)
   681  	c.Assert(cacheEP, gc.DeepEquals, state.Endpoint{
   682  		ServiceName: "wordpress",
   683  		Relation: charm.Relation{
   684  			Interface: "varnish",
   685  			Name:      "cache",
   686  			Role:      charm.RoleRequirer,
   687  			Scope:     charm.ScopeGlobal,
   688  			Limit:     2,
   689  			Optional:  true,
   690  		},
   691  	})
   692  
   693  	eps, err := wordpress.Endpoints()
   694  	c.Assert(err, gc.IsNil)
   695  	c.Assert(eps, gc.DeepEquals, []state.Endpoint{cacheEP, dbEP, jiEP, ldEP, mpEP, urlEP})
   696  }
   697  
   698  func (s *ServiceSuite) TestServiceRefresh(c *gc.C) {
   699  	s1, err := s.State.Service(s.mysql.Name())
   700  	c.Assert(err, gc.IsNil)
   701  
   702  	err = s.mysql.SetCharm(s.charm, true)
   703  	c.Assert(err, gc.IsNil)
   704  
   705  	testch, force, err := s1.Charm()
   706  	c.Assert(err, gc.IsNil)
   707  	c.Assert(force, gc.Equals, false)
   708  	c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL())
   709  
   710  	err = s1.Refresh()
   711  	c.Assert(err, gc.IsNil)
   712  	testch, force, err = s1.Charm()
   713  	c.Assert(err, gc.IsNil)
   714  	c.Assert(force, gc.Equals, true)
   715  	c.Assert(testch.URL(), gc.DeepEquals, s.charm.URL())
   716  
   717  	err = s.mysql.Destroy()
   718  	c.Assert(err, gc.IsNil)
   719  	err = s.mysql.Refresh()
   720  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   721  }
   722  
   723  func (s *ServiceSuite) TestServiceExposed(c *gc.C) {
   724  	// Check that querying for the exposed flag works correctly.
   725  	c.Assert(s.mysql.IsExposed(), gc.Equals, false)
   726  
   727  	// Check that setting and clearing the exposed flag works correctly.
   728  	err := s.mysql.SetExposed()
   729  	c.Assert(err, gc.IsNil)
   730  	c.Assert(s.mysql.IsExposed(), gc.Equals, true)
   731  	err = s.mysql.ClearExposed()
   732  	c.Assert(err, gc.IsNil)
   733  	c.Assert(s.mysql.IsExposed(), gc.Equals, false)
   734  
   735  	// Check that setting and clearing the exposed flag repeatedly does not fail.
   736  	err = s.mysql.SetExposed()
   737  	c.Assert(err, gc.IsNil)
   738  	err = s.mysql.SetExposed()
   739  	c.Assert(err, gc.IsNil)
   740  	err = s.mysql.ClearExposed()
   741  	c.Assert(err, gc.IsNil)
   742  	err = s.mysql.ClearExposed()
   743  	c.Assert(err, gc.IsNil)
   744  	err = s.mysql.SetExposed()
   745  	c.Assert(err, gc.IsNil)
   746  	c.Assert(s.mysql.IsExposed(), gc.Equals, true)
   747  
   748  	// Make the service Dying and check that ClearExposed and SetExposed fail.
   749  	// TODO(fwereade): maybe service destruction should always unexpose?
   750  	u, err := s.mysql.AddUnit()
   751  	c.Assert(err, gc.IsNil)
   752  	err = s.mysql.Destroy()
   753  	c.Assert(err, gc.IsNil)
   754  	err = s.mysql.ClearExposed()
   755  	c.Assert(err, gc.ErrorMatches, notAliveErr)
   756  	err = s.mysql.SetExposed()
   757  	c.Assert(err, gc.ErrorMatches, notAliveErr)
   758  
   759  	// Remove the service and check that both fail.
   760  	err = u.EnsureDead()
   761  	c.Assert(err, gc.IsNil)
   762  	err = u.Remove()
   763  	c.Assert(err, gc.IsNil)
   764  	err = s.mysql.SetExposed()
   765  	c.Assert(err, gc.ErrorMatches, notAliveErr)
   766  	err = s.mysql.ClearExposed()
   767  	c.Assert(err, gc.ErrorMatches, notAliveErr)
   768  }
   769  
   770  func (s *ServiceSuite) TestAddUnit(c *gc.C) {
   771  	// Check that principal units can be added on their own.
   772  	unitZero, err := s.mysql.AddUnit()
   773  	c.Assert(err, gc.IsNil)
   774  	c.Assert(unitZero.Name(), gc.Equals, "mysql/0")
   775  	c.Assert(unitZero.IsPrincipal(), gc.Equals, true)
   776  	c.Assert(unitZero.SubordinateNames(), gc.HasLen, 0)
   777  	unitOne, err := s.mysql.AddUnit()
   778  	c.Assert(err, gc.IsNil)
   779  	c.Assert(unitOne.Name(), gc.Equals, "mysql/1")
   780  	c.Assert(unitOne.IsPrincipal(), gc.Equals, true)
   781  	c.Assert(unitOne.SubordinateNames(), gc.HasLen, 0)
   782  
   783  	// Assign the principal unit to a machine.
   784  	m, err := s.State.AddMachine("quantal", state.JobHostUnits)
   785  	c.Assert(err, gc.IsNil)
   786  	err = unitZero.AssignToMachine(m)
   787  	c.Assert(err, gc.IsNil)
   788  
   789  	// Add a subordinate service and check that units cannot be added directly.
   790  	// to add a subordinate unit.
   791  	subCharm := s.AddTestingCharm(c, "logging")
   792  	logging := s.AddTestingService(c, "logging", subCharm)
   793  	_, err = logging.AddUnit()
   794  	c.Assert(err, gc.ErrorMatches, `cannot add unit to service "logging": service is a subordinate`)
   795  
   796  	// Indirectly create a subordinate unit by adding a relation and entering
   797  	// scope as a principal.
   798  	eps, err := s.State.InferEndpoints([]string{"logging", "mysql"})
   799  	c.Assert(err, gc.IsNil)
   800  	rel, err := s.State.AddRelation(eps...)
   801  	c.Assert(err, gc.IsNil)
   802  	ru, err := rel.Unit(unitZero)
   803  	c.Assert(err, gc.IsNil)
   804  	err = ru.EnterScope(nil)
   805  	c.Assert(err, gc.IsNil)
   806  	subZero, err := s.State.Unit("logging/0")
   807  	c.Assert(err, gc.IsNil)
   808  
   809  	// Check that once it's refreshed unitZero has subordinates.
   810  	err = unitZero.Refresh()
   811  	c.Assert(err, gc.IsNil)
   812  	c.Assert(unitZero.SubordinateNames(), gc.DeepEquals, []string{"logging/0"})
   813  
   814  	// Check the subordinate unit has been assigned its principal's machine.
   815  	id, err := subZero.AssignedMachineId()
   816  	c.Assert(err, gc.IsNil)
   817  	c.Assert(id, gc.Equals, m.Id())
   818  }
   819  
   820  func (s *ServiceSuite) TestAddUnitWhenNotAlive(c *gc.C) {
   821  	u, err := s.mysql.AddUnit()
   822  	c.Assert(err, gc.IsNil)
   823  	err = s.mysql.Destroy()
   824  	c.Assert(err, gc.IsNil)
   825  	_, err = s.mysql.AddUnit()
   826  	c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service is not alive`)
   827  	err = u.EnsureDead()
   828  	c.Assert(err, gc.IsNil)
   829  	err = u.Remove()
   830  	c.Assert(err, gc.IsNil)
   831  	_, err = s.mysql.AddUnit()
   832  	c.Assert(err, gc.ErrorMatches, `cannot add unit to service "mysql": service "mysql" not found`)
   833  }
   834  
   835  func (s *ServiceSuite) TestReadUnit(c *gc.C) {
   836  	_, err := s.mysql.AddUnit()
   837  	c.Assert(err, gc.IsNil)
   838  	_, err = s.mysql.AddUnit()
   839  	c.Assert(err, gc.IsNil)
   840  
   841  	// Check that retrieving a unit from the service works correctly.
   842  	unit, err := s.mysql.Unit("mysql/0")
   843  	c.Assert(err, gc.IsNil)
   844  	c.Assert(unit.Name(), gc.Equals, "mysql/0")
   845  
   846  	// Check that retrieving a unit from state works correctly.
   847  	unit, err = s.State.Unit("mysql/0")
   848  	c.Assert(err, gc.IsNil)
   849  	c.Assert(unit.Name(), gc.Equals, "mysql/0")
   850  
   851  	// Check that retrieving a non-existent or an invalidly
   852  	// named unit fail nicely.
   853  	unit, err = s.mysql.Unit("mysql")
   854  	c.Assert(err, gc.ErrorMatches, `"mysql" is not a valid unit name`)
   855  	unit, err = s.mysql.Unit("mysql/0/0")
   856  	c.Assert(err, gc.ErrorMatches, `"mysql/0/0" is not a valid unit name`)
   857  	unit, err = s.mysql.Unit("pressword/0")
   858  	c.Assert(err, gc.ErrorMatches, `cannot get unit "pressword/0" from service "mysql": .*`)
   859  
   860  	// Check direct state retrieval also fails nicely.
   861  	unit, err = s.State.Unit("mysql")
   862  	c.Assert(err, gc.ErrorMatches, `"mysql" is not a valid unit name`)
   863  	unit, err = s.State.Unit("mysql/0/0")
   864  	c.Assert(err, gc.ErrorMatches, `"mysql/0/0" is not a valid unit name`)
   865  	unit, err = s.State.Unit("pressword/0")
   866  	c.Assert(err, gc.ErrorMatches, `unit "pressword/0" not found`)
   867  
   868  	// Add another service to check units are not misattributed.
   869  	mysql := s.AddTestingService(c, "wordpress", s.charm)
   870  	_, err = mysql.AddUnit()
   871  	c.Assert(err, gc.IsNil)
   872  
   873  	// BUG(aram): use error strings from state.
   874  	unit, err = s.mysql.Unit("wordpress/0")
   875  	c.Assert(err, gc.ErrorMatches, `cannot get unit "wordpress/0" from service "mysql": .*`)
   876  
   877  	units, err := s.mysql.AllUnits()
   878  	c.Assert(err, gc.IsNil)
   879  	c.Assert(sortedUnitNames(units), gc.DeepEquals, []string{"mysql/0", "mysql/1"})
   880  }
   881  
   882  func (s *ServiceSuite) TestReadUnitWhenDying(c *gc.C) {
   883  	// Test that we can still read units when the service is Dying...
   884  	unit, err := s.mysql.AddUnit()
   885  	c.Assert(err, gc.IsNil)
   886  	preventUnitDestroyRemove(c, unit)
   887  	err = s.mysql.Destroy()
   888  	c.Assert(err, gc.IsNil)
   889  	_, err = s.mysql.AllUnits()
   890  	c.Assert(err, gc.IsNil)
   891  	_, err = s.mysql.Unit("mysql/0")
   892  	c.Assert(err, gc.IsNil)
   893  
   894  	// ...and when those units are Dying or Dead...
   895  	testWhenDying(c, unit, noErr, noErr, func() error {
   896  		_, err := s.mysql.AllUnits()
   897  		return err
   898  	}, func() error {
   899  		_, err := s.mysql.Unit("mysql/0")
   900  		return err
   901  	})
   902  
   903  	// ...and even, in a very limited way, when the service itself is removed.
   904  	removeAllUnits(c, s.mysql)
   905  	_, err = s.mysql.AllUnits()
   906  	c.Assert(err, gc.IsNil)
   907  }
   908  
   909  func (s *ServiceSuite) TestDestroySimple(c *gc.C) {
   910  	err := s.mysql.Destroy()
   911  	c.Assert(err, gc.IsNil)
   912  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
   913  	err = s.mysql.Refresh()
   914  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   915  }
   916  
   917  func (s *ServiceSuite) TestDestroyStillHasUnits(c *gc.C) {
   918  	unit, err := s.mysql.AddUnit()
   919  	c.Assert(err, gc.IsNil)
   920  	err = s.mysql.Destroy()
   921  	c.Assert(err, gc.IsNil)
   922  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
   923  
   924  	err = unit.EnsureDead()
   925  	c.Assert(err, gc.IsNil)
   926  	err = s.mysql.Refresh()
   927  	c.Assert(err, gc.IsNil)
   928  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
   929  
   930  	err = unit.Remove()
   931  	c.Assert(err, gc.IsNil)
   932  	err = s.mysql.Refresh()
   933  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   934  }
   935  
   936  func (s *ServiceSuite) TestDestroyOnceHadUnits(c *gc.C) {
   937  	unit, err := s.mysql.AddUnit()
   938  	c.Assert(err, gc.IsNil)
   939  	err = unit.EnsureDead()
   940  	c.Assert(err, gc.IsNil)
   941  	err = unit.Remove()
   942  	c.Assert(err, gc.IsNil)
   943  
   944  	err = s.mysql.Destroy()
   945  	c.Assert(err, gc.IsNil)
   946  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
   947  	err = s.mysql.Refresh()
   948  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   949  }
   950  
   951  func (s *ServiceSuite) TestDestroyStaleNonZeroUnitCount(c *gc.C) {
   952  	unit, err := s.mysql.AddUnit()
   953  	c.Assert(err, gc.IsNil)
   954  	err = s.mysql.Refresh()
   955  	c.Assert(err, gc.IsNil)
   956  	err = unit.EnsureDead()
   957  	c.Assert(err, gc.IsNil)
   958  	err = unit.Remove()
   959  	c.Assert(err, gc.IsNil)
   960  
   961  	err = s.mysql.Destroy()
   962  	c.Assert(err, gc.IsNil)
   963  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
   964  	err = s.mysql.Refresh()
   965  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   966  }
   967  
   968  func (s *ServiceSuite) TestDestroyStaleZeroUnitCount(c *gc.C) {
   969  	unit, err := s.mysql.AddUnit()
   970  	c.Assert(err, gc.IsNil)
   971  
   972  	err = s.mysql.Destroy()
   973  	c.Assert(err, gc.IsNil)
   974  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
   975  
   976  	err = s.mysql.Refresh()
   977  	c.Assert(err, gc.IsNil)
   978  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
   979  
   980  	err = unit.EnsureDead()
   981  	c.Assert(err, gc.IsNil)
   982  	err = s.mysql.Refresh()
   983  	c.Assert(err, gc.IsNil)
   984  	c.Assert(s.mysql.Life(), gc.Equals, state.Dying)
   985  
   986  	err = unit.Remove()
   987  	c.Assert(err, gc.IsNil)
   988  	err = s.mysql.Refresh()
   989  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   990  }
   991  
   992  func (s *ServiceSuite) TestDestroyWithRemovableRelation(c *gc.C) {
   993  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   994  	eps, err := s.State.InferEndpoints([]string{"wordpress", "mysql"})
   995  	c.Assert(err, gc.IsNil)
   996  	rel, err := s.State.AddRelation(eps...)
   997  	c.Assert(err, gc.IsNil)
   998  
   999  	// Destroy a service with no units in relation scope; check service and
  1000  	// unit removed.
  1001  	err = wordpress.Destroy()
  1002  	c.Assert(err, gc.IsNil)
  1003  	err = wordpress.Refresh()
  1004  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1005  	err = rel.Refresh()
  1006  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1007  }
  1008  
  1009  func (s *ServiceSuite) TestDestroyWithReferencedRelation(c *gc.C) {
  1010  	s.assertDestroyWithReferencedRelation(c, true)
  1011  }
  1012  
  1013  func (s *ServiceSuite) TestDestroyWithreferencedRelationStaleCount(c *gc.C) {
  1014  	s.assertDestroyWithReferencedRelation(c, false)
  1015  }
  1016  
  1017  func (s *ServiceSuite) assertDestroyWithReferencedRelation(c *gc.C, refresh bool) {
  1018  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1019  	eps, err := s.State.InferEndpoints([]string{"wordpress", "mysql"})
  1020  	c.Assert(err, gc.IsNil)
  1021  	rel0, err := s.State.AddRelation(eps...)
  1022  	c.Assert(err, gc.IsNil)
  1023  
  1024  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
  1025  	eps, err = s.State.InferEndpoints([]string{"logging", "mysql"})
  1026  	c.Assert(err, gc.IsNil)
  1027  	rel1, err := s.State.AddRelation(eps...)
  1028  	c.Assert(err, gc.IsNil)
  1029  
  1030  	// Add a separate reference to the first relation.
  1031  	unit, err := wordpress.AddUnit()
  1032  	c.Assert(err, gc.IsNil)
  1033  	ru, err := rel0.Unit(unit)
  1034  	c.Assert(err, gc.IsNil)
  1035  	err = ru.EnterScope(nil)
  1036  	c.Assert(err, gc.IsNil)
  1037  
  1038  	// Optionally update the service document to get correct relation counts.
  1039  	if refresh {
  1040  		err = s.mysql.Destroy()
  1041  		c.Assert(err, gc.IsNil)
  1042  	}
  1043  
  1044  	// Destroy, and check that the first relation becomes Dying...
  1045  	err = s.mysql.Destroy()
  1046  	c.Assert(err, gc.IsNil)
  1047  	err = rel0.Refresh()
  1048  	c.Assert(err, gc.IsNil)
  1049  	c.Assert(rel0.Life(), gc.Equals, state.Dying)
  1050  
  1051  	// ...while the second is removed directly.
  1052  	err = rel1.Refresh()
  1053  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1054  
  1055  	// Drop the last reference to the first relation; check the relation and
  1056  	// the service are are both removed.
  1057  	err = ru.LeaveScope()
  1058  	c.Assert(err, gc.IsNil)
  1059  	err = s.mysql.Refresh()
  1060  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1061  	err = rel0.Refresh()
  1062  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1063  }
  1064  
  1065  func (s *ServiceSuite) TestDestroyQueuesUnitCleanup(c *gc.C) {
  1066  	// Add 5 units; block quick-remove of mysql/1 and mysql/3
  1067  	units := make([]*state.Unit, 5)
  1068  	for i := range units {
  1069  		unit, err := s.mysql.AddUnit()
  1070  		c.Assert(err, gc.IsNil)
  1071  		units[i] = unit
  1072  		if i%2 != 0 {
  1073  			preventUnitDestroyRemove(c, unit)
  1074  		}
  1075  	}
  1076  
  1077  	// Check state is clean.
  1078  	dirty, err := s.State.NeedsCleanup()
  1079  	c.Assert(err, gc.IsNil)
  1080  	c.Assert(dirty, gc.Equals, false)
  1081  
  1082  	// Destroy mysql, and check units are not touched.
  1083  	err = s.mysql.Destroy()
  1084  	c.Assert(err, gc.IsNil)
  1085  	for _, unit := range units {
  1086  		assertLife(c, unit, state.Alive)
  1087  	}
  1088  
  1089  	// Check a cleanup doc was added.
  1090  	dirty, err = s.State.NeedsCleanup()
  1091  	c.Assert(err, gc.IsNil)
  1092  	c.Assert(dirty, gc.Equals, true)
  1093  
  1094  	// Run the cleanup and check the units.
  1095  	err = s.State.Cleanup()
  1096  	c.Assert(err, gc.IsNil)
  1097  	for i, unit := range units {
  1098  		if i%2 != 0 {
  1099  			assertLife(c, unit, state.Dying)
  1100  		} else {
  1101  			assertRemoved(c, unit)
  1102  		}
  1103  	}
  1104  
  1105  	// Check for queued unit cleanups, and run them.
  1106  	dirty, err = s.State.NeedsCleanup()
  1107  	c.Assert(err, gc.IsNil)
  1108  	c.Assert(dirty, gc.Equals, true)
  1109  	err = s.State.Cleanup()
  1110  	c.Assert(err, gc.IsNil)
  1111  
  1112  	// Check we're now clean.
  1113  	dirty, err = s.State.NeedsCleanup()
  1114  	c.Assert(err, gc.IsNil)
  1115  	c.Assert(dirty, gc.Equals, false)
  1116  }
  1117  
  1118  func (s *ServiceSuite) TestReadUnitWithChangingState(c *gc.C) {
  1119  	// Check that reading a unit after removing the service
  1120  	// fails nicely.
  1121  	err := s.mysql.Destroy()
  1122  	c.Assert(err, gc.IsNil)
  1123  	err = s.mysql.Refresh()
  1124  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1125  	_, err = s.State.Unit("mysql/0")
  1126  	c.Assert(err, gc.ErrorMatches, `unit "mysql/0" not found`)
  1127  }
  1128  
  1129  func uint64p(val uint64) *uint64 {
  1130  	return &val
  1131  }
  1132  
  1133  func (s *ServiceSuite) TestConstraints(c *gc.C) {
  1134  	// Constraints are initially empty (for now).
  1135  	cons, err := s.mysql.Constraints()
  1136  	c.Assert(err, gc.IsNil)
  1137  	c.Assert(&cons, jc.Satisfies, constraints.IsEmpty)
  1138  
  1139  	// Constraints can be set.
  1140  	cons2 := constraints.Value{Mem: uint64p(4096)}
  1141  	err = s.mysql.SetConstraints(cons2)
  1142  	cons3, err := s.mysql.Constraints()
  1143  	c.Assert(err, gc.IsNil)
  1144  	c.Assert(cons3, gc.DeepEquals, cons2)
  1145  
  1146  	// Constraints are completely overwritten when re-set.
  1147  	cons4 := constraints.Value{CpuPower: uint64p(750)}
  1148  	err = s.mysql.SetConstraints(cons4)
  1149  	c.Assert(err, gc.IsNil)
  1150  	cons5, err := s.mysql.Constraints()
  1151  	c.Assert(err, gc.IsNil)
  1152  	c.Assert(cons5, gc.DeepEquals, cons4)
  1153  
  1154  	// Destroy the existing service; there's no way to directly assert
  1155  	// that the constraints are deleted...
  1156  	err = s.mysql.Destroy()
  1157  	c.Assert(err, gc.IsNil)
  1158  	err = s.mysql.Refresh()
  1159  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1160  
  1161  	// ...but we can check that old constraints do not affect new services
  1162  	// with matching names.
  1163  	ch, _, err := s.mysql.Charm()
  1164  	c.Assert(err, gc.IsNil)
  1165  	mysql := s.AddTestingService(c, s.mysql.Name(), ch)
  1166  	cons6, err := mysql.Constraints()
  1167  	c.Assert(err, gc.IsNil)
  1168  	c.Assert(&cons6, jc.Satisfies, constraints.IsEmpty)
  1169  }
  1170  
  1171  func (s *ServiceSuite) TestSetInvalidConstraints(c *gc.C) {
  1172  	cons := constraints.MustParse("mem=4G instance-type=foo")
  1173  	err := s.mysql.SetConstraints(cons)
  1174  	c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "instance-type" overlaps with "mem"`)
  1175  }
  1176  
  1177  func (s *ServiceSuite) TestSetUnsupportedConstraintsWarning(c *gc.C) {
  1178  	defer loggo.ResetWriters()
  1179  	logger := loggo.GetLogger("test")
  1180  	logger.SetLogLevel(loggo.DEBUG)
  1181  	tw := &loggo.TestWriter{}
  1182  	c.Assert(loggo.RegisterWriter("constraints-tester", tw, loggo.DEBUG), gc.IsNil)
  1183  
  1184  	cons := constraints.MustParse("mem=4G cpu-power=10")
  1185  	err := s.mysql.SetConstraints(cons)
  1186  	c.Assert(err, gc.IsNil)
  1187  	c.Assert(tw.Log, jc.LogMatches, jc.SimpleMessages{{
  1188  		loggo.WARNING,
  1189  		`setting constraints on service "mysql": unsupported constraints: cpu-power`},
  1190  	})
  1191  	scons, err := s.mysql.Constraints()
  1192  	c.Assert(err, gc.IsNil)
  1193  	c.Assert(scons, gc.DeepEquals, cons)
  1194  }
  1195  
  1196  func (s *ServiceSuite) TestConstraintsLifecycle(c *gc.C) {
  1197  	// Dying.
  1198  	unit, err := s.mysql.AddUnit()
  1199  	c.Assert(err, gc.IsNil)
  1200  	err = s.mysql.Destroy()
  1201  	c.Assert(err, gc.IsNil)
  1202  	cons1 := constraints.MustParse("mem=1G")
  1203  	err = s.mysql.SetConstraints(cons1)
  1204  	c.Assert(err, gc.ErrorMatches, `cannot set constraints: not found or not alive`)
  1205  	scons, err := s.mysql.Constraints()
  1206  	c.Assert(err, gc.IsNil)
  1207  	c.Assert(&scons, jc.Satisfies, constraints.IsEmpty)
  1208  
  1209  	// Removed (== Dead, for a service).
  1210  	err = unit.EnsureDead()
  1211  	c.Assert(err, gc.IsNil)
  1212  	err = unit.Remove()
  1213  	c.Assert(err, gc.IsNil)
  1214  	err = s.mysql.SetConstraints(cons1)
  1215  	c.Assert(err, gc.ErrorMatches, `cannot set constraints: not found or not alive`)
  1216  	_, err = s.mysql.Constraints()
  1217  	c.Assert(err, gc.ErrorMatches, `constraints not found`)
  1218  }
  1219  
  1220  func (s *ServiceSuite) TestSubordinateConstraints(c *gc.C) {
  1221  	loggingCh := s.AddTestingCharm(c, "logging")
  1222  	logging := s.AddTestingService(c, "logging", loggingCh)
  1223  
  1224  	_, err := logging.Constraints()
  1225  	c.Assert(err, gc.Equals, state.ErrSubordinateConstraints)
  1226  
  1227  	err = logging.SetConstraints(constraints.Value{})
  1228  	c.Assert(err, gc.Equals, state.ErrSubordinateConstraints)
  1229  }
  1230  
  1231  func (s *ServiceSuite) TestWatchUnitsBulkEvents(c *gc.C) {
  1232  	// Alive unit...
  1233  	alive, err := s.mysql.AddUnit()
  1234  	c.Assert(err, gc.IsNil)
  1235  
  1236  	// Dying unit...
  1237  	dying, err := s.mysql.AddUnit()
  1238  	c.Assert(err, gc.IsNil)
  1239  	preventUnitDestroyRemove(c, dying)
  1240  	err = dying.Destroy()
  1241  	c.Assert(err, gc.IsNil)
  1242  
  1243  	// Dead unit...
  1244  	dead, err := s.mysql.AddUnit()
  1245  	c.Assert(err, gc.IsNil)
  1246  	preventUnitDestroyRemove(c, dead)
  1247  	err = dead.Destroy()
  1248  	c.Assert(err, gc.IsNil)
  1249  	err = dead.EnsureDead()
  1250  	c.Assert(err, gc.IsNil)
  1251  
  1252  	// Gone unit.
  1253  	gone, err := s.mysql.AddUnit()
  1254  	c.Assert(err, gc.IsNil)
  1255  	err = gone.Destroy()
  1256  	c.Assert(err, gc.IsNil)
  1257  
  1258  	// All except gone unit are reported in initial event.
  1259  	w := s.mysql.WatchUnits()
  1260  	defer testing.AssertStop(c, w)
  1261  	wc := testing.NewStringsWatcherC(c, s.State, w)
  1262  	wc.AssertChange(alive.Name(), dying.Name(), dead.Name())
  1263  	wc.AssertNoChange()
  1264  
  1265  	// Remove them all; alive/dying changes reported; dead never mentioned again.
  1266  	err = alive.Destroy()
  1267  	c.Assert(err, gc.IsNil)
  1268  	err = dying.EnsureDead()
  1269  	c.Assert(err, gc.IsNil)
  1270  	err = dying.Remove()
  1271  	c.Assert(err, gc.IsNil)
  1272  	err = dead.Remove()
  1273  	c.Assert(err, gc.IsNil)
  1274  	wc.AssertChange(alive.Name(), dying.Name())
  1275  	wc.AssertNoChange()
  1276  }
  1277  
  1278  func (s *ServiceSuite) TestWatchUnitsLifecycle(c *gc.C) {
  1279  	// Empty initial event when no units.
  1280  	w := s.mysql.WatchUnits()
  1281  	defer testing.AssertStop(c, w)
  1282  	wc := testing.NewStringsWatcherC(c, s.State, w)
  1283  	wc.AssertChange()
  1284  	wc.AssertNoChange()
  1285  
  1286  	// Create one unit, check one change.
  1287  	quick, err := s.mysql.AddUnit()
  1288  	c.Assert(err, gc.IsNil)
  1289  	wc.AssertChange(quick.Name())
  1290  	wc.AssertNoChange()
  1291  
  1292  	// Destroy that unit (short-circuited to removal), check one change.
  1293  	err = quick.Destroy()
  1294  	c.Assert(err, gc.IsNil)
  1295  	wc.AssertChange(quick.Name())
  1296  	wc.AssertNoChange()
  1297  
  1298  	// Create another, check one change.
  1299  	slow, err := s.mysql.AddUnit()
  1300  	c.Assert(err, gc.IsNil)
  1301  	wc.AssertChange(slow.Name())
  1302  	wc.AssertNoChange()
  1303  
  1304  	// Change unit itself, no change.
  1305  	preventUnitDestroyRemove(c, slow)
  1306  	wc.AssertNoChange()
  1307  
  1308  	// Make unit Dying, change detected.
  1309  	err = slow.Destroy()
  1310  	c.Assert(err, gc.IsNil)
  1311  	wc.AssertChange(slow.Name())
  1312  	wc.AssertNoChange()
  1313  
  1314  	// Make unit Dead, change detected.
  1315  	err = slow.EnsureDead()
  1316  	c.Assert(err, gc.IsNil)
  1317  	wc.AssertChange(slow.Name())
  1318  	wc.AssertNoChange()
  1319  
  1320  	// Remove unit, final change not detected.
  1321  	err = slow.Remove()
  1322  	c.Assert(err, gc.IsNil)
  1323  	wc.AssertNoChange()
  1324  }
  1325  
  1326  func (s *ServiceSuite) TestWatchRelations(c *gc.C) {
  1327  	// TODO(fwereade) split this test up a bit.
  1328  	w := s.mysql.WatchRelations()
  1329  	defer testing.AssertStop(c, w)
  1330  	wc := testing.NewStringsWatcherC(c, s.State, w)
  1331  	wc.AssertChange()
  1332  	wc.AssertNoChange()
  1333  
  1334  	// Add a relation; check change.
  1335  	mysqlep, err := s.mysql.Endpoint("server")
  1336  	c.Assert(err, gc.IsNil)
  1337  	wpch := s.AddTestingCharm(c, "wordpress")
  1338  	wpi := 0
  1339  	addRelation := func() *state.Relation {
  1340  		name := fmt.Sprintf("wp%d", wpi)
  1341  		wpi++
  1342  		wp := s.AddTestingService(c, name, wpch)
  1343  		wpep, err := wp.Endpoint("db")
  1344  		c.Assert(err, gc.IsNil)
  1345  		rel, err := s.State.AddRelation(mysqlep, wpep)
  1346  		c.Assert(err, gc.IsNil)
  1347  		return rel
  1348  	}
  1349  	rel0 := addRelation()
  1350  	wc.AssertChange(rel0.String())
  1351  	wc.AssertNoChange()
  1352  
  1353  	// Add another relation; check change.
  1354  	rel1 := addRelation()
  1355  	wc.AssertChange(rel1.String())
  1356  	wc.AssertNoChange()
  1357  
  1358  	// Destroy a relation; check change.
  1359  	err = rel0.Destroy()
  1360  	c.Assert(err, gc.IsNil)
  1361  	wc.AssertChange(rel0.String())
  1362  	wc.AssertNoChange()
  1363  
  1364  	// Stop watcher; check change chan is closed.
  1365  	testing.AssertStop(c, w)
  1366  	wc.AssertClosed()
  1367  
  1368  	// Add a new relation; start a new watcher; check initial event.
  1369  	rel2 := addRelation()
  1370  	w = s.mysql.WatchRelations()
  1371  	defer testing.AssertStop(c, w)
  1372  	wc = testing.NewStringsWatcherC(c, s.State, w)
  1373  	wc.AssertChange(rel1.String(), rel2.String())
  1374  	wc.AssertNoChange()
  1375  
  1376  	// Add a unit to the new relation; check no change.
  1377  	unit, err := s.mysql.AddUnit()
  1378  	c.Assert(err, gc.IsNil)
  1379  	ru2, err := rel2.Unit(unit)
  1380  	c.Assert(err, gc.IsNil)
  1381  	err = ru2.EnterScope(nil)
  1382  	c.Assert(err, gc.IsNil)
  1383  	wc.AssertNoChange()
  1384  
  1385  	// Destroy the relation with the unit in scope, and add another; check
  1386  	// changes.
  1387  	err = rel2.Destroy()
  1388  	c.Assert(err, gc.IsNil)
  1389  	rel3 := addRelation()
  1390  	wc.AssertChange(rel2.String(), rel3.String())
  1391  	wc.AssertNoChange()
  1392  
  1393  	// Leave scope, destroying the relation, and check that change as well.
  1394  	err = ru2.LeaveScope()
  1395  	c.Assert(err, gc.IsNil)
  1396  	wc.AssertChange(rel2.String())
  1397  	wc.AssertNoChange()
  1398  }
  1399  
  1400  func removeAllUnits(c *gc.C, s *state.Service) {
  1401  	us, err := s.AllUnits()
  1402  	c.Assert(err, gc.IsNil)
  1403  	for _, u := range us {
  1404  		err = u.EnsureDead()
  1405  		c.Assert(err, gc.IsNil)
  1406  		err = u.Remove()
  1407  		c.Assert(err, gc.IsNil)
  1408  	}
  1409  }
  1410  
  1411  func (s *ServiceSuite) TestWatchService(c *gc.C) {
  1412  	w := s.mysql.Watch()
  1413  	defer testing.AssertStop(c, w)
  1414  
  1415  	// Initial event.
  1416  	wc := testing.NewNotifyWatcherC(c, s.State, w)
  1417  	wc.AssertOneChange()
  1418  
  1419  	// Make one change (to a separate instance), check one event.
  1420  	service, err := s.State.Service(s.mysql.Name())
  1421  	c.Assert(err, gc.IsNil)
  1422  	err = service.SetExposed()
  1423  	c.Assert(err, gc.IsNil)
  1424  	wc.AssertOneChange()
  1425  
  1426  	// Make two changes, check one event.
  1427  	err = service.ClearExposed()
  1428  	c.Assert(err, gc.IsNil)
  1429  	err = service.SetCharm(s.charm, true)
  1430  	c.Assert(err, gc.IsNil)
  1431  	wc.AssertOneChange()
  1432  
  1433  	// Stop, check closed.
  1434  	testing.AssertStop(c, w)
  1435  	wc.AssertClosed()
  1436  
  1437  	// Remove service, start new watch, check single event.
  1438  	err = service.Destroy()
  1439  	c.Assert(err, gc.IsNil)
  1440  	w = s.mysql.Watch()
  1441  	defer testing.AssertStop(c, w)
  1442  	testing.NewNotifyWatcherC(c, s.State, w).AssertOneChange()
  1443  }
  1444  
  1445  func (s *ServiceSuite) TestAnnotatorForService(c *gc.C) {
  1446  	testAnnotator(c, func() (state.Annotator, error) {
  1447  		return s.State.Service("mysql")
  1448  	})
  1449  }
  1450  
  1451  func (s *ServiceSuite) TestAnnotationRemovalForService(c *gc.C) {
  1452  	annotations := map[string]string{"mykey": "myvalue"}
  1453  	err := s.mysql.SetAnnotations(annotations)
  1454  	c.Assert(err, gc.IsNil)
  1455  	err = s.mysql.Destroy()
  1456  	c.Assert(err, gc.IsNil)
  1457  	ann, err := s.mysql.Annotations()
  1458  	c.Assert(err, gc.IsNil)
  1459  	c.Assert(ann, gc.DeepEquals, make(map[string]string))
  1460  }
  1461  
  1462  // SCHEMACHANGE
  1463  // TODO(mattyw) remove when schema upgrades are possible
  1464  // Check that GetOwnerTag returns user-admin even
  1465  // when the service has no owner
  1466  func (s *ServiceSuite) TestOwnerTagSchemaProtection(c *gc.C) {
  1467  	service := s.AddTestingService(c, "foobar", s.charm)
  1468  	state.SetServiceOwnerTag(service, "")
  1469  	c.Assert(state.GetServiceOwnerTag(service), gc.Equals, "")
  1470  	c.Assert(service.GetOwnerTag(), gc.Equals, "user-admin")
  1471  }
  1472  
  1473  func (s *ServiceSuite) TestNetworks(c *gc.C) {
  1474  	service, err := s.State.Service(s.mysql.Name())
  1475  	c.Assert(err, gc.IsNil)
  1476  	networks, err := service.Networks()
  1477  	c.Assert(err, gc.IsNil)
  1478  	c.Check(networks, gc.HasLen, 0)
  1479  }
  1480  
  1481  func (s *ServiceSuite) TestNetworksOnService(c *gc.C) {
  1482  	networks := []string{"yes", "on"}
  1483  	service := s.AddTestingServiceWithNetworks(c, "withnets", s.charm, networks)
  1484  	requestedNetworks, err := service.Networks()
  1485  	c.Assert(err, gc.IsNil)
  1486  	c.Check(requestedNetworks, gc.DeepEquals, networks)
  1487  }