Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package state_test
import (
"fmt"
"net/url"
"strconv"
"strings"
"time"
"github.com/juju/loggo"
jc "github.com/juju/testing/checkers"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
gc "launchpad.net/gocheck"
"github.com/wallyworld/core/charm"
"github.com/wallyworld/core/constraints"
"github.com/wallyworld/core/environs/config"
"github.com/wallyworld/core/errors"
"github.com/wallyworld/core/instance"
"github.com/wallyworld/core/names"
"github.com/wallyworld/core/replicaset"
"github.com/wallyworld/core/state"
"github.com/wallyworld/core/state/api/params"
statetesting "github.com/wallyworld/core/state/testing"
"github.com/wallyworld/core/testing"
"github.com/wallyworld/core/utils"
"github.com/wallyworld/core/version"
)
var goodPassword = "foo-12345678901234567890"
var alternatePassword = "bar-12345678901234567890"
// preventUnitDestroyRemove sets a non-pending status on the unit, and hence
// prevents it from being unceremoniously removed from state on Destroy. This
// is useful because several tests go through a unit's lifecycle step by step,
// asserting the behaviour of a given method in each state, and the unit quick-
// remove change caused many of these to fail.
func preventUnitDestroyRemove(c *gc.C, u *state.Unit) {
err := u.SetStatus(params.StatusStarted, "", nil)
c.Assert(err, gc.IsNil)
}
type StateSuite struct {
ConnSuite
}
var _ = gc.Suite(&StateSuite{})
func (s *StateSuite) SetUpTest(c *gc.C) {
s.ConnSuite.SetUpTest(c)
s.policy.getConstraintsValidator = func(*config.Config) (constraints.Validator, error) {
validator := constraints.NewValidator()
validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem})
validator.RegisterUnsupported([]string{constraints.CpuPower})
return validator, nil
}
}
func (s *StateSuite) TestDialAgain(c *gc.C) {
// Ensure idempotent operations on Dial are working fine.
for i := 0; i < 2; i++ {
st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil))
c.Assert(err, gc.IsNil)
c.Assert(st.Close(), gc.IsNil)
}
}
func (s *StateSuite) TestMongoSession(c *gc.C) {
session := s.State.MongoSession()
c.Assert(session.Ping(), gc.IsNil)
}
func (s *StateSuite) TestAddresses(c *gc.C) {
var err error
machines := make([]*state.Machine, 4)
machines[0], err = s.State.AddMachine("quantal", state.JobManageEnviron, state.JobHostUnits)
c.Assert(err, gc.IsNil)
machines[1], err = s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
err = s.State.EnsureAvailability(3, constraints.Value{}, "quantal")
c.Assert(err, gc.IsNil)
machines[2], err = s.State.Machine("2")
c.Assert(err, gc.IsNil)
machines[3], err = s.State.Machine("3")
c.Assert(err, gc.IsNil)
for i, m := range machines {
err := m.SetAddresses(instance.Address{
Type: instance.Ipv4Address,
NetworkScope: instance.NetworkCloudLocal,
Value: fmt.Sprintf("10.0.0.%d", i),
}, instance.Address{
Type: instance.Ipv6Address,
NetworkScope: instance.NetworkCloudLocal,
Value: "::1",
}, instance.Address{
Type: instance.Ipv4Address,
NetworkScope: instance.NetworkMachineLocal,
Value: "127.0.0.1",
}, instance.Address{
Type: instance.Ipv4Address,
NetworkScope: instance.NetworkPublic,
Value: "5.4.3.2",
})
c.Assert(err, gc.IsNil)
}
envConfig, err := s.State.EnvironConfig()
c.Assert(err, gc.IsNil)
addrs, err := s.State.Addresses()
c.Assert(err, gc.IsNil)
c.Assert(addrs, gc.HasLen, 3)
c.Assert(addrs, jc.SameContents, []string{
fmt.Sprintf("10.0.0.0:%d", envConfig.StatePort()),
fmt.Sprintf("10.0.0.2:%d", envConfig.StatePort()),
fmt.Sprintf("10.0.0.3:%d", envConfig.StatePort()),
})
addrs, err = s.State.APIAddressesFromMachines()
c.Assert(err, gc.IsNil)
c.Assert(addrs, gc.HasLen, 3)
c.Assert(addrs, jc.SameContents, []string{
fmt.Sprintf("10.0.0.0:%d", envConfig.APIPort()),
fmt.Sprintf("10.0.0.2:%d", envConfig.APIPort()),
fmt.Sprintf("10.0.0.3:%d", envConfig.APIPort()),
})
}
func (s *StateSuite) TestPing(c *gc.C) {
c.Assert(s.State.Ping(), gc.IsNil)
testing.MgoServer.Restart()
c.Assert(s.State.Ping(), gc.NotNil)
}
func (s *StateSuite) TestIsNotFound(c *gc.C) {
err1 := fmt.Errorf("unrelated error")
err2 := errors.NotFoundf("foo")
c.Assert(err1, gc.Not(jc.Satisfies), errors.IsNotFound)
c.Assert(err2, jc.Satisfies, errors.IsNotFound)
}
func (s *StateSuite) dummyCharm(c *gc.C, curlOverride string) (ch charm.Charm, curl *charm.URL, bundleURL *url.URL, bundleSHA256 string) {
var err error
ch = testing.Charms.Dir("dummy")
if curlOverride != "" {
curl = charm.MustParseURL(curlOverride)
} else {
curl = charm.MustParseURL(
fmt.Sprintf("local:quantal/%s-%d", ch.Meta().Name, ch.Revision()),
)
}
bundleURL, err = url.Parse("http://bundles.testing.invalid/dummy-1")
c.Assert(err, gc.IsNil)
bundleSHA256 = "dummy-1-sha256"
return ch, curl, bundleURL, bundleSHA256
}
func (s *StateSuite) TestAddCharm(c *gc.C) {
// Check that adding charms from scratch works correctly.
ch, curl, bundleURL, bundleSHA256 := s.dummyCharm(c, "")
dummy, err := s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
c.Assert(err, gc.IsNil)
c.Assert(dummy.URL().String(), gc.Equals, curl.String())
doc := state.CharmDoc{}
err = s.charms.FindId(curl).One(&doc)
c.Assert(err, gc.IsNil)
c.Logf("%#v", doc)
c.Assert(doc.URL, gc.DeepEquals, curl)
}
func (s *StateSuite) TestAddCharmUpdatesPlaceholder(c *gc.C) {
// Check that adding charms updates any existing placeholder charm
// with the same URL.
ch := testing.Charms.Dir("dummy")
// Add a placeholder charm.
curl := charm.MustParseURL("cs:quantal/dummy-1")
err := s.State.AddStoreCharmPlaceholder(curl)
c.Assert(err, gc.IsNil)
// Add a deployed charm.
bundleURL, err := url.Parse("http://bundles.testing.invalid/dummy-1")
c.Assert(err, gc.IsNil)
bundleSHA256 := "dummy-1-sha256"
dummy, err := s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
c.Assert(err, gc.IsNil)
c.Assert(dummy.URL().String(), gc.Equals, curl.String())
// Charm doc has been updated.
var docs []state.CharmDoc
err = s.charms.FindId(curl).All(&docs)
c.Assert(err, gc.IsNil)
c.Assert(docs, gc.HasLen, 1)
c.Assert(docs[0].URL, gc.DeepEquals, curl)
c.Assert(docs[0].BundleURL, gc.DeepEquals, bundleURL)
// No more placeholder charm.
_, err = s.State.LatestPlaceholderCharm(curl)
c.Assert(err, jc.Satisfies, errors.IsNotFound)
}
func (s *StateSuite) assertPendingCharmExists(c *gc.C, curl *charm.URL) {
// Find charm directly and verify only the charm URL and
// PendingUpload are set.
doc := state.CharmDoc{}
err := s.charms.FindId(curl).One(&doc)
c.Assert(err, gc.IsNil)
c.Logf("%#v", doc)
c.Assert(doc.URL, gc.DeepEquals, curl)
c.Assert(doc.PendingUpload, jc.IsTrue)
c.Assert(doc.Placeholder, jc.IsFalse)
c.Assert(doc.Meta, gc.IsNil)
c.Assert(doc.Config, gc.IsNil)
c.Assert(doc.BundleURL, gc.IsNil)
c.Assert(doc.BundleSha256, gc.Equals, "")
// Make sure we can't find it with st.Charm().
_, err = s.State.Charm(curl)
c.Assert(err, jc.Satisfies, errors.IsNotFound)
}
func (s *StateSuite) TestPrepareLocalCharmUpload(c *gc.C) {
// First test the sanity checks.
curl, err := s.State.PrepareLocalCharmUpload(charm.MustParseURL("local:quantal/dummy"))
c.Assert(err, gc.ErrorMatches, "expected charm URL with revision, got .*")
c.Assert(curl, gc.IsNil)
curl, err = s.State.PrepareLocalCharmUpload(charm.MustParseURL("cs:quantal/dummy"))
c.Assert(err, gc.ErrorMatches, "expected charm URL with local schema, got .*")
c.Assert(curl, gc.IsNil)
// No charm in state, so the call should respect given revision.
testCurl := charm.MustParseURL("local:quantal/missing-123")
curl, err = s.State.PrepareLocalCharmUpload(testCurl)
c.Assert(err, gc.IsNil)
c.Assert(curl, gc.DeepEquals, testCurl)
s.assertPendingCharmExists(c, curl)
// Try adding it again with the same revision and ensure it gets bumped.
curl, err = s.State.PrepareLocalCharmUpload(curl)
c.Assert(err, gc.IsNil)
c.Assert(curl.Revision, gc.Equals, 124)
// Also ensure the revision cannot decrease.
curl, err = s.State.PrepareLocalCharmUpload(curl.WithRevision(42))
c.Assert(err, gc.IsNil)
c.Assert(curl.Revision, gc.Equals, 125)
// Check the given revision is respected.
curl, err = s.State.PrepareLocalCharmUpload(curl.WithRevision(1234))
c.Assert(err, gc.IsNil)
c.Assert(curl.Revision, gc.Equals, 1234)
}
func (s *StateSuite) TestPrepareStoreCharmUpload(c *gc.C) {
// First test the sanity checks.
sch, err := s.State.PrepareStoreCharmUpload(charm.MustParseURL("cs:quantal/dummy"))
c.Assert(err, gc.ErrorMatches, "expected charm URL with revision, got .*")
c.Assert(sch, gc.IsNil)
sch, err = s.State.PrepareStoreCharmUpload(charm.MustParseURL("local:quantal/dummy"))
c.Assert(err, gc.ErrorMatches, "expected charm URL with cs schema, got .*")
c.Assert(sch, gc.IsNil)
// No charm in state, so the call should respect given revision.
testCurl := charm.MustParseURL("cs:quantal/missing-123")
sch, err = s.State.PrepareStoreCharmUpload(testCurl)
c.Assert(err, gc.IsNil)
c.Assert(sch.URL(), gc.DeepEquals, testCurl)
c.Assert(sch.IsUploaded(), jc.IsFalse)
s.assertPendingCharmExists(c, sch.URL())
// Try adding it again with the same revision and ensure we get the same document.
schCopy, err := s.State.PrepareStoreCharmUpload(testCurl)
c.Assert(err, gc.IsNil)
c.Assert(sch, jc.DeepEquals, schCopy)
// Now add a charm and try again - we should get the same result
// as with AddCharm.
ch, curl, bundleURL, bundleSHA256 := s.dummyCharm(c, "cs:precise/dummy-2")
sch, err = s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
c.Assert(err, gc.IsNil)
schCopy, err = s.State.PrepareStoreCharmUpload(curl)
c.Assert(err, gc.IsNil)
c.Assert(sch, jc.DeepEquals, schCopy)
// Finally, try poking around the state with a placeholder and
// bundlesha256 to make sure we do the right thing.
curl = curl.WithRevision(999)
first := state.TransactionHook{
Before: func() {
err := s.State.AddStoreCharmPlaceholder(curl)
c.Assert(err, gc.IsNil)
},
After: func() {
err := s.charms.RemoveId(curl)
c.Assert(err, gc.IsNil)
},
}
second := state.TransactionHook{
Before: func() {
err := s.State.AddStoreCharmPlaceholder(curl)
c.Assert(err, gc.IsNil)
},
After: func() {
err := s.charms.UpdateId(curl, bson.D{{"$set", bson.D{
{"bundlesha256", "fake"}},
}})
c.Assert(err, gc.IsNil)
},
}
defer state.SetTransactionHooks(
c, s.State, first, second, first,
).Check()
_, err = s.State.PrepareStoreCharmUpload(curl)
c.Assert(err, gc.Equals, state.ErrExcessiveContention)
}
func (s *StateSuite) TestUpdateUploadedCharm(c *gc.C) {
ch, curl, bundleURL, bundleSHA256 := s.dummyCharm(c, "")
_, err := s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
c.Assert(err, gc.IsNil)
// Test with already uploaded and a missing charms.
sch, err := s.State.UpdateUploadedCharm(ch, curl, bundleURL, bundleSHA256)
c.Assert(err, gc.ErrorMatches, fmt.Sprintf("charm %q already uploaded", curl))
c.Assert(sch, gc.IsNil)
missingCurl := charm.MustParseURL("local:quantal/missing-1")
sch, err = s.State.UpdateUploadedCharm(ch, missingCurl, bundleURL, "missing")
c.Assert(err, jc.Satisfies, errors.IsNotFound)
c.Assert(sch, gc.IsNil)
// Test with with an uploaded local charm.
_, err = s.State.PrepareLocalCharmUpload(missingCurl)
c.Assert(err, gc.IsNil)
sch, err = s.State.UpdateUploadedCharm(ch, missingCurl, bundleURL, "missing")
c.Assert(err, gc.IsNil)
c.Assert(sch.URL(), gc.DeepEquals, missingCurl)
c.Assert(sch.Revision(), gc.Equals, missingCurl.Revision)
c.Assert(sch.IsUploaded(), jc.IsTrue)
c.Assert(sch.IsPlaceholder(), jc.IsFalse)
c.Assert(sch.Meta(), gc.DeepEquals, ch.Meta())
c.Assert(sch.Config(), gc.DeepEquals, ch.Config())
c.Assert(sch.BundleURL(), gc.DeepEquals, bundleURL)
c.Assert(sch.BundleSha256(), gc.Equals, "missing")
}
func (s *StateSuite) assertPlaceholderCharmExists(c *gc.C, curl *charm.URL) {
// Find charm directly and verify only the charm URL and
// Placeholder are set.
doc := state.CharmDoc{}
err := s.charms.FindId(curl).One(&doc)
c.Assert(err, gc.IsNil)
c.Assert(doc.URL, gc.DeepEquals, curl)
c.Assert(doc.PendingUpload, jc.IsFalse)
c.Assert(doc.Placeholder, jc.IsTrue)
c.Assert(doc.Meta, gc.IsNil)
c.Assert(doc.Config, gc.IsNil)
c.Assert(doc.BundleURL, gc.IsNil)
c.Assert(doc.BundleSha256, gc.Equals, "")
// Make sure we can't find it with st.Charm().
_, err = s.State.Charm(curl)
c.Assert(err, jc.Satisfies, errors.IsNotFound)
}
func (s *StateSuite) TestLatestPlaceholderCharm(c *gc.C) {
// Add a deployed charm
ch, curl, bundleURL, bundleSHA256 := s.dummyCharm(c, "cs:quantal/dummy-1")
_, err := s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
c.Assert(err, gc.IsNil)
// Deployed charm not found.
_, err = s.State.LatestPlaceholderCharm(curl)
c.Assert(err, jc.Satisfies, errors.IsNotFound)
// Add a charm reference
curl2 := charm.MustParseURL("cs:quantal/dummy-2")
err = s.State.AddStoreCharmPlaceholder(curl2)
c.Assert(err, gc.IsNil)
s.assertPlaceholderCharmExists(c, curl2)
// Use a URL with an arbitrary rev to search.
curl = charm.MustParseURL("cs:quantal/dummy-23")
pending, err := s.State.LatestPlaceholderCharm(curl)
c.Assert(err, gc.IsNil)
c.Assert(pending.URL(), gc.DeepEquals, curl2)
c.Assert(pending.IsPlaceholder(), jc.IsTrue)
c.Assert(pending.Meta(), gc.IsNil)
c.Assert(pending.Config(), gc.IsNil)
c.Assert(pending.BundleURL(), gc.IsNil)
c.Assert(pending.BundleSha256(), gc.Equals, "")
}
func (s *StateSuite) TestAddStoreCharmPlaceholderErrors(c *gc.C) {
ch := testing.Charms.Dir("dummy")
curl := charm.MustParseURL(
fmt.Sprintf("local:quantal/%s-%d", ch.Meta().Name, ch.Revision()),
)
err := s.State.AddStoreCharmPlaceholder(curl)
c.Assert(err, gc.ErrorMatches, "expected charm URL with cs schema, got .*")
curl = charm.MustParseURL("cs:quantal/dummy")
err = s.State.AddStoreCharmPlaceholder(curl)
c.Assert(err, gc.ErrorMatches, "expected charm URL with revision, got .*")
}
func (s *StateSuite) TestAddStoreCharmPlaceholder(c *gc.C) {
curl := charm.MustParseURL("cs:quantal/dummy-1")
err := s.State.AddStoreCharmPlaceholder(curl)
c.Assert(err, gc.IsNil)
s.assertPlaceholderCharmExists(c, curl)
// Add the same one again, should be a no-op
err = s.State.AddStoreCharmPlaceholder(curl)
c.Assert(err, gc.IsNil)
s.assertPlaceholderCharmExists(c, curl)
}
func (s *StateSuite) assertAddStoreCharmPlaceholder(c *gc.C) (*charm.URL, *charm.URL, *state.Charm) {
// Add a deployed charm
ch, curl, bundleURL, bundleSHA256 := s.dummyCharm(c, "cs:quantal/dummy-1")
dummy, err := s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
c.Assert(err, gc.IsNil)
// Add a charm placeholder
curl2 := charm.MustParseURL("cs:quantal/dummy-2")
err = s.State.AddStoreCharmPlaceholder(curl2)
c.Assert(err, gc.IsNil)
s.assertPlaceholderCharmExists(c, curl2)
// Deployed charm is still there.
existing, err := s.State.Charm(curl)
c.Assert(err, gc.IsNil)
c.Assert(existing, jc.DeepEquals, dummy)
return curl, curl2, dummy
}
func (s *StateSuite) TestAddStoreCharmPlaceholderLeavesDeployedCharmsAlone(c *gc.C) {
s.assertAddStoreCharmPlaceholder(c)
}
func (s *StateSuite) TestAddStoreCharmPlaceholderDeletesOlder(c *gc.C) {
curl, curlOldRef, dummy := s.assertAddStoreCharmPlaceholder(c)
// Add a new charm placeholder
curl3 := charm.MustParseURL("cs:quantal/dummy-3")
err := s.State.AddStoreCharmPlaceholder(curl3)
c.Assert(err, gc.IsNil)
s.assertPlaceholderCharmExists(c, curl3)
// Deployed charm is still there.
existing, err := s.State.Charm(curl)
c.Assert(err, gc.IsNil)
c.Assert(existing, jc.DeepEquals, dummy)
// Older charm placeholder is gone.
doc := state.CharmDoc{}
err = s.charms.FindId(curlOldRef).One(&doc)
c.Assert(err, gc.Equals, mgo.ErrNotFound)
}
func (s *StateSuite) AssertMachineCount(c *gc.C, expect int) {
ms, err := s.State.AllMachines()
c.Assert(err, gc.IsNil)
c.Assert(len(ms), gc.Equals, expect)
}
var jobStringTests = []struct {
job state.MachineJob
s string
}{
{state.JobHostUnits, "JobHostUnits"},
{state.JobManageEnviron, "JobManageEnviron"},
{state.JobManageStateDeprecated, "JobManageState"},
{0, "<unknown job 0>"},
{5, "<unknown job 5>"},
}
func (s *StateSuite) TestJobString(c *gc.C) {
for _, t := range jobStringTests {
c.Check(t.job.String(), gc.Equals, t.s)
}
}
func (s *StateSuite) TestAddMachineErrors(c *gc.C) {
_, err := s.State.AddMachine("")
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: no series specified")
_, err = s.State.AddMachine("quantal")
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: no jobs specified")
_, err = s.State.AddMachine("quantal", state.JobHostUnits, state.JobHostUnits)
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: duplicate job: .*")
}
func (s *StateSuite) TestAddMachine(c *gc.C) {
allJobs := []state.MachineJob{
state.JobHostUnits,
state.JobManageEnviron,
}
m0, err := s.State.AddMachine("quantal", allJobs...)
c.Assert(err, gc.IsNil)
check := func(m *state.Machine, id, series string, jobs []state.MachineJob) {
c.Assert(m.Id(), gc.Equals, id)
c.Assert(m.Series(), gc.Equals, series)
c.Assert(m.Jobs(), gc.DeepEquals, jobs)
s.assertMachineContainers(c, m, nil)
}
check(m0, "0", "quantal", allJobs)
m0, err = s.State.Machine("0")
c.Assert(err, gc.IsNil)
check(m0, "0", "quantal", allJobs)
oneJob := []state.MachineJob{state.JobHostUnits}
m1, err := s.State.AddMachine("blahblah", oneJob...)
c.Assert(err, gc.IsNil)
check(m1, "1", "blahblah", oneJob)
m1, err = s.State.Machine("1")
c.Assert(err, gc.IsNil)
check(m1, "1", "blahblah", oneJob)
m, err := s.State.AllMachines()
c.Assert(err, gc.IsNil)
c.Assert(m, gc.HasLen, 2)
check(m[0], "0", "quantal", allJobs)
check(m[1], "1", "blahblah", oneJob)
}
func (s *StateSuite) TestAddMachines(c *gc.C) {
oneJob := []state.MachineJob{state.JobHostUnits}
cons := constraints.MustParse("mem=4G")
hc := instance.MustParseHardware("mem=2G")
machineTemplate := state.MachineTemplate{
Series: "precise",
Constraints: cons,
HardwareCharacteristics: hc,
InstanceId: "inst-id",
Nonce: "nonce",
Jobs: oneJob,
}
machines, err := s.State.AddMachines(machineTemplate)
c.Assert(err, gc.IsNil)
c.Assert(machines, gc.HasLen, 1)
m, err := s.State.Machine(machines[0].Id())
c.Assert(err, gc.IsNil)
instId, err := m.InstanceId()
c.Assert(err, gc.IsNil)
c.Assert(string(instId), gc.Equals, "inst-id")
c.Assert(m.CheckProvisioned("nonce"), jc.IsTrue)
c.Assert(m.Series(), gc.Equals, "precise")
mcons, err := m.Constraints()
c.Assert(err, gc.IsNil)
c.Assert(mcons, gc.DeepEquals, cons)
mhc, err := m.HardwareCharacteristics()
c.Assert(err, gc.IsNil)
c.Assert(*mhc, gc.DeepEquals, hc)
// Clear the deprecated machineDoc InstanceId attribute and do it again.
// still works as expected with the new data model.
state.SetMachineInstanceId(m, "")
instId, err = m.InstanceId()
c.Assert(err, gc.IsNil)
c.Assert(string(instId), gc.Equals, "inst-id")
}
func (s *StateSuite) TestAddMachinesEnvironmentDying(c *gc.C) {
_, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
env, err := s.State.Environment()
c.Assert(err, gc.IsNil)
err = env.Destroy()
c.Assert(err, gc.IsNil)
// Check that machines cannot be added if the environment is initially Dying.
_, err = s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: environment is no longer alive")
}
func (s *StateSuite) TestAddMachinesEnvironmentDyingAfterInitial(c *gc.C) {
_, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
env, err := s.State.Environment()
c.Assert(err, gc.IsNil)
// Check that machines cannot be added if the environment is initially
// Alive but set to Dying immediately before the transaction is run.
defer state.SetBeforeHooks(c, s.State, func() {
c.Assert(env.Life(), gc.Equals, state.Alive)
c.Assert(env.Destroy(), gc.IsNil)
}).Check()
_, err = s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: environment is no longer alive")
}
func (s *StateSuite) TestAddMachineExtraConstraints(c *gc.C) {
err := s.State.SetEnvironConstraints(constraints.MustParse("mem=4G"))
c.Assert(err, gc.IsNil)
oneJob := []state.MachineJob{state.JobHostUnits}
extraCons := constraints.MustParse("cpu-cores=4")
m, err := s.State.AddOneMachine(state.MachineTemplate{
Series: "quantal",
Constraints: extraCons,
Jobs: oneJob,
})
c.Assert(err, gc.IsNil)
c.Assert(m.Id(), gc.Equals, "0")
c.Assert(m.Series(), gc.Equals, "quantal")
c.Assert(m.Jobs(), gc.DeepEquals, oneJob)
expectedCons := constraints.MustParse("cpu-cores=4 mem=4G")
mcons, err := m.Constraints()
c.Assert(err, gc.IsNil)
c.Assert(mcons, gc.DeepEquals, expectedCons)
}
func (s *StateSuite) assertMachineContainers(c *gc.C, m *state.Machine, containers []string) {
mc, err := m.Containers()
c.Assert(err, gc.IsNil)
c.Assert(mc, gc.DeepEquals, containers)
}
func (s *StateSuite) TestAddContainerToNewMachine(c *gc.C) {
oneJob := []state.MachineJob{state.JobHostUnits}
template := state.MachineTemplate{
Series: "quantal",
Jobs: oneJob,
}
parentTemplate := state.MachineTemplate{
Series: "raring",
Jobs: oneJob,
}
m, err := s.State.AddMachineInsideNewMachine(template, parentTemplate, instance.LXC)
c.Assert(err, gc.IsNil)
c.Assert(m.Id(), gc.Equals, "0/lxc/0")
c.Assert(m.Series(), gc.Equals, "quantal")
c.Assert(m.ContainerType(), gc.Equals, instance.LXC)
mcons, err := m.Constraints()
c.Assert(err, gc.IsNil)
c.Assert(&mcons, jc.Satisfies, constraints.IsEmpty)
c.Assert(m.Jobs(), gc.DeepEquals, oneJob)
m, err = s.State.Machine("0")
c.Assert(err, gc.IsNil)
s.assertMachineContainers(c, m, []string{"0/lxc/0"})
c.Assert(m.Series(), gc.Equals, "raring")
m, err = s.State.Machine("0/lxc/0")
c.Assert(err, gc.IsNil)
s.assertMachineContainers(c, m, nil)
c.Assert(m.Jobs(), gc.DeepEquals, oneJob)
}
func (s *StateSuite) TestAddContainerToExistingMachine(c *gc.C) {
oneJob := []state.MachineJob{state.JobHostUnits}
m0, err := s.State.AddMachine("quantal", oneJob...)
c.Assert(err, gc.IsNil)
m1, err := s.State.AddMachine("quantal", oneJob...)
c.Assert(err, gc.IsNil)
// Add first container.
m, err := s.State.AddMachineInsideMachine(state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}, "1", instance.LXC)
c.Assert(err, gc.IsNil)
c.Assert(m.Id(), gc.Equals, "1/lxc/0")
c.Assert(m.Series(), gc.Equals, "quantal")
c.Assert(m.ContainerType(), gc.Equals, instance.LXC)
mcons, err := m.Constraints()
c.Assert(err, gc.IsNil)
c.Assert(&mcons, jc.Satisfies, constraints.IsEmpty)
c.Assert(m.Jobs(), gc.DeepEquals, oneJob)
s.assertMachineContainers(c, m1, []string{"1/lxc/0"})
s.assertMachineContainers(c, m0, nil)
s.assertMachineContainers(c, m1, []string{"1/lxc/0"})
m, err = s.State.Machine("1/lxc/0")
c.Assert(err, gc.IsNil)
s.assertMachineContainers(c, m, nil)
// Add second container.
m, err = s.State.AddMachineInsideMachine(state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}, "1", instance.LXC)
c.Assert(err, gc.IsNil)
c.Assert(m.Id(), gc.Equals, "1/lxc/1")
c.Assert(m.Series(), gc.Equals, "quantal")
c.Assert(m.ContainerType(), gc.Equals, instance.LXC)
c.Assert(m.Jobs(), gc.DeepEquals, oneJob)
s.assertMachineContainers(c, m1, []string{"1/lxc/0", "1/lxc/1"})
}
func (s *StateSuite) TestAddContainerToMachineWithKnownSupportedContainers(c *gc.C) {
oneJob := []state.MachineJob{state.JobHostUnits}
host, err := s.State.AddMachine("quantal", oneJob...)
c.Assert(err, gc.IsNil)
err = host.SetSupportedContainers([]instance.ContainerType{instance.KVM})
c.Assert(err, gc.IsNil)
m, err := s.State.AddMachineInsideMachine(state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}, "0", instance.KVM)
c.Assert(err, gc.IsNil)
c.Assert(m.Id(), gc.Equals, "0/kvm/0")
s.assertMachineContainers(c, host, []string{"0/kvm/0"})
}
func (s *StateSuite) TestAddInvalidContainerToMachineWithKnownSupportedContainers(c *gc.C) {
oneJob := []state.MachineJob{state.JobHostUnits}
host, err := s.State.AddMachine("quantal", oneJob...)
c.Assert(err, gc.IsNil)
err = host.SetSupportedContainers([]instance.ContainerType{instance.KVM})
c.Assert(err, gc.IsNil)
_, err = s.State.AddMachineInsideMachine(state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}, "0", instance.LXC)
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host lxc containers")
s.assertMachineContainers(c, host, nil)
}
func (s *StateSuite) TestAddContainerToMachineSupportingNoContainers(c *gc.C) {
oneJob := []state.MachineJob{state.JobHostUnits}
host, err := s.State.AddMachine("quantal", oneJob...)
c.Assert(err, gc.IsNil)
err = host.SupportsNoContainers()
c.Assert(err, gc.IsNil)
_, err = s.State.AddMachineInsideMachine(state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}, "0", instance.LXC)
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host lxc containers")
s.assertMachineContainers(c, host, nil)
}
func (s *StateSuite) TestInvalidAddMachineParams(c *gc.C) {
instIdTemplate := state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
InstanceId: "i-foo",
}
normalTemplate := state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}
_, err := s.State.AddMachineInsideMachine(instIdTemplate, "0", instance.LXC)
c.Check(err, gc.ErrorMatches, "cannot add a new machine: cannot specify instance id for a new container")
_, err = s.State.AddMachineInsideNewMachine(instIdTemplate, normalTemplate, instance.LXC)
c.Check(err, gc.ErrorMatches, "cannot add a new machine: cannot specify instance id for a new container")
_, err = s.State.AddMachineInsideNewMachine(normalTemplate, instIdTemplate, instance.LXC)
c.Check(err, gc.ErrorMatches, "cannot add a new machine: cannot specify instance id for a new container")
_, err = s.State.AddOneMachine(instIdTemplate)
c.Check(err, gc.ErrorMatches, "cannot add a new machine: cannot add a machine with an instance id and no nonce")
_, err = s.State.AddOneMachine(state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits, state.JobHostUnits},
InstanceId: "i-foo",
Nonce: "nonce",
})
c.Check(err, gc.ErrorMatches, fmt.Sprintf("cannot add a new machine: duplicate job: %s", state.JobHostUnits))
noSeriesTemplate := state.MachineTemplate{
Jobs: []state.MachineJob{state.JobHostUnits, state.JobHostUnits},
}
_, err = s.State.AddOneMachine(noSeriesTemplate)
c.Check(err, gc.ErrorMatches, "cannot add a new machine: no series specified")
_, err = s.State.AddMachineInsideNewMachine(noSeriesTemplate, normalTemplate, instance.LXC)
c.Check(err, gc.ErrorMatches, "cannot add a new machine: no series specified")
_, err = s.State.AddMachineInsideNewMachine(normalTemplate, noSeriesTemplate, instance.LXC)
c.Check(err, gc.ErrorMatches, "cannot add a new machine: no series specified")
_, err = s.State.AddMachineInsideMachine(noSeriesTemplate, "0", instance.LXC)
c.Check(err, gc.ErrorMatches, "cannot add a new machine: no series specified")
}
func (s *StateSuite) TestAddContainerErrors(c *gc.C) {
template := state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}
_, err := s.State.AddMachineInsideMachine(template, "10", instance.LXC)
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: machine 10 not found")
_, err = s.State.AddMachineInsideMachine(template, "10", "")
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: no container type specified")
}
func (s *StateSuite) TestInjectMachineErrors(c *gc.C) {
injectMachine := func(series string, instanceId instance.Id, nonce string, jobs ...state.MachineJob) error {
_, err := s.State.AddOneMachine(state.MachineTemplate{
Series: series,
Jobs: jobs,
InstanceId: instanceId,
Nonce: nonce,
})
return err
}
err := injectMachine("", "i-minvalid", state.BootstrapNonce, state.JobHostUnits)
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: no series specified")
err = injectMachine("quantal", "", state.BootstrapNonce, state.JobHostUnits)
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: cannot specify a nonce without an instance id")
err = injectMachine("quantal", "i-minvalid", "", state.JobHostUnits)
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: cannot add a machine with an instance id and no nonce")
err = injectMachine("quantal", state.BootstrapNonce, "i-mlazy")
c.Assert(err, gc.ErrorMatches, "cannot add a new machine: no jobs specified")
}
func (s *StateSuite) TestInjectMachine(c *gc.C) {
cons := constraints.MustParse("mem=4G")
arch := "amd64"
mem := uint64(1024)
disk := uint64(1024)
tags := []string{"foo", "bar"}
template := state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits, state.JobManageEnviron},
Constraints: cons,
InstanceId: "i-mindustrious",
Nonce: state.BootstrapNonce,
HardwareCharacteristics: instance.HardwareCharacteristics{
Arch: &arch,
Mem: &mem,
RootDisk: &disk,
Tags: &tags,
},
}
m, err := s.State.AddOneMachine(template)
c.Assert(err, gc.IsNil)
c.Assert(m.Jobs(), gc.DeepEquals, template.Jobs)
instanceId, err := m.InstanceId()
c.Assert(err, gc.IsNil)
c.Assert(instanceId, gc.Equals, template.InstanceId)
mcons, err := m.Constraints()
c.Assert(err, gc.IsNil)
c.Assert(cons, gc.DeepEquals, mcons)
characteristics, err := m.HardwareCharacteristics()
c.Assert(err, gc.IsNil)
c.Assert(*characteristics, gc.DeepEquals, template.HardwareCharacteristics)
// Make sure the bootstrap nonce value is set.
c.Assert(m.CheckProvisioned(template.Nonce), gc.Equals, true)
}
func (s *StateSuite) TestAddContainerToInjectedMachine(c *gc.C) {
oneJob := []state.MachineJob{state.JobHostUnits}
template := state.MachineTemplate{
Series: "quantal",
InstanceId: "i-mindustrious",
Nonce: state.BootstrapNonce,
Jobs: []state.MachineJob{state.JobHostUnits, state.JobManageEnviron},
}
m0, err := s.State.AddOneMachine(template)
c.Assert(err, gc.IsNil)
// Add first container.
template = state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}
m, err := s.State.AddMachineInsideMachine(template, "0", instance.LXC)
c.Assert(err, gc.IsNil)
c.Assert(m.Id(), gc.Equals, "0/lxc/0")
c.Assert(m.Series(), gc.Equals, "quantal")
c.Assert(m.ContainerType(), gc.Equals, instance.LXC)
mcons, err := m.Constraints()
c.Assert(err, gc.IsNil)
c.Assert(&mcons, jc.Satisfies, constraints.IsEmpty)
c.Assert(m.Jobs(), gc.DeepEquals, oneJob)
s.assertMachineContainers(c, m0, []string{"0/lxc/0"})
// Add second container.
m, err = s.State.AddMachineInsideMachine(template, "0", instance.LXC)
c.Assert(err, gc.IsNil)
c.Assert(m.Id(), gc.Equals, "0/lxc/1")
c.Assert(m.Series(), gc.Equals, "quantal")
c.Assert(m.ContainerType(), gc.Equals, instance.LXC)
c.Assert(m.Jobs(), gc.DeepEquals, oneJob)
s.assertMachineContainers(c, m0, []string{"0/lxc/0", "0/lxc/1"})
}
func (s *StateSuite) TestAddMachineCanOnlyAddStateServerForMachine0(c *gc.C) {
template := state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobManageEnviron},
}
// Check that we can add the bootstrap machine.
m, err := s.State.AddOneMachine(template)
c.Assert(err, gc.IsNil)
c.Assert(m.Id(), gc.Equals, "0")
c.Assert(m.WantsVote(), jc.IsTrue)
c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageEnviron})
// Check that the state server information is correct.
info, err := s.State.StateServerInfo()
c.Assert(err, gc.IsNil)
c.Assert(info.MachineIds, gc.DeepEquals, []string{"0"})
c.Assert(info.VotingMachineIds, gc.DeepEquals, []string{"0"})
const errCannotAdd = "cannot add a new machine: state server jobs specified without calling EnsureAvailability"
m, err = s.State.AddOneMachine(template)
c.Assert(err, gc.ErrorMatches, errCannotAdd)
m, err = s.State.AddMachineInsideMachine(template, "0", instance.LXC)
c.Assert(err, gc.ErrorMatches, errCannotAdd)
m, err = s.State.AddMachineInsideNewMachine(template, template, instance.LXC)
c.Assert(err, gc.ErrorMatches, errCannotAdd)
}
func (s *StateSuite) TestReadMachine(c *gc.C) {
machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
expectedId := machine.Id()
machine, err = s.State.Machine(expectedId)
c.Assert(err, gc.IsNil)
c.Assert(machine.Id(), gc.Equals, expectedId)
}
func (s *StateSuite) TestMachineNotFound(c *gc.C) {
_, err := s.State.Machine("0")
c.Assert(err, gc.ErrorMatches, "machine 0 not found")
c.Assert(err, jc.Satisfies, errors.IsNotFound)
}
func (s *StateSuite) TestMachineIdLessThan(c *gc.C) {
c.Assert(state.MachineIdLessThan("0", "0"), gc.Equals, false)
c.Assert(state.MachineIdLessThan("0", "1"), gc.Equals, true)
c.Assert(state.MachineIdLessThan("1", "0"), gc.Equals, false)
c.Assert(state.MachineIdLessThan("10", "2"), gc.Equals, false)
c.Assert(state.MachineIdLessThan("0", "0/lxc/0"), gc.Equals, true)
c.Assert(state.MachineIdLessThan("0/lxc/0", "0"), gc.Equals, false)
c.Assert(state.MachineIdLessThan("1", "0/lxc/0"), gc.Equals, false)
c.Assert(state.MachineIdLessThan("0/lxc/0", "1"), gc.Equals, true)
c.Assert(state.MachineIdLessThan("0/lxc/0/lxc/1", "0/lxc/0"), gc.Equals, false)
c.Assert(state.MachineIdLessThan("0/kvm/0", "0/lxc/0"), gc.Equals, true)
}
func (s *StateSuite) TestAllMachines(c *gc.C) {
numInserts := 42
for i := 0; i < numInserts; i++ {
m, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
err = m.SetProvisioned(instance.Id(fmt.Sprintf("foo-%d", i)), "fake_nonce", nil)
c.Assert(err, gc.IsNil)
err = m.SetAgentVersion(version.MustParseBinary("7.8.9-foo-bar"))
c.Assert(err, gc.IsNil)
err = m.Destroy()
c.Assert(err, gc.IsNil)
}
s.AssertMachineCount(c, numInserts)
ms, _ := s.State.AllMachines()
for i, m := range ms {
c.Assert(m.Id(), gc.Equals, strconv.Itoa(i))
instId, err := m.InstanceId()
c.Assert(err, gc.IsNil)
c.Assert(string(instId), gc.Equals, fmt.Sprintf("foo-%d", i))
tools, err := m.AgentTools()
c.Check(err, gc.IsNil)
c.Check(tools.Version, gc.DeepEquals, version.MustParseBinary("7.8.9-foo-bar"))
c.Assert(m.Life(), gc.Equals, state.Dying)
}
}
var addNetworkErrorsTests = []struct {
args state.NetworkInfo
expectErr string
}{{
state.NetworkInfo{"", "provider-id", "0.3.1.0/24", 0},
`cannot add network "": name must be not empty`,
}, {
state.NetworkInfo{"-invalid-", "provider-id", "0.3.1.0/24", 0},
`cannot add network "-invalid-": invalid name`,
}, {
state.NetworkInfo{"net2", "", "0.3.1.0/24", 0},
`cannot add network "net2": provider id must be not empty`,
}, {
state.NetworkInfo{"net2", "provider-id", "invalid", 0},
`cannot add network "net2": invalid CIDR address: invalid`,
}, {
state.NetworkInfo{"net2", "provider-id", "0.3.1.0/24", -1},
`cannot add network "net2": invalid VLAN tag -1: must be between 0 and 4094`,
}, {
state.NetworkInfo{"net2", "provider-id", "0.3.1.0/24", 9999},
`cannot add network "net2": invalid VLAN tag 9999: must be between 0 and 4094`,
}, {
state.NetworkInfo{"net1", "provider-id", "0.3.1.0/24", 0},
`cannot add network "net1": network "net1" already exists`,
}, {
state.NetworkInfo{"net2", "provider-net1", "0.3.1.0/24", 0},
`cannot add network "net2": network with provider id "provider-net1" already exists`,
}}
func (s *StateSuite) TestAddNetworkErrors(c *gc.C) {
machine, err := s.State.AddOneMachine(state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
IncludeNetworks: []string{"net1", "net2"},
ExcludeNetworks: []string{"net3", "net4"},
})
c.Assert(err, gc.IsNil)
net1, _ := addNetworkAndInterface(
c, s.State, machine,
"net1", "provider-net1", "0.1.2.0/24", 0, false,
"aa:bb:cc:dd:ee:f0", "eth0")
net, err := s.State.Network("net1")
c.Assert(err, gc.IsNil)
c.Assert(net, gc.DeepEquals, net1)
c.Assert(net.Name(), gc.Equals, "net1")
c.Assert(string(net.ProviderId()), gc.Equals, "provider-net1")
_, err = s.State.Network("missing")
c.Assert(err, jc.Satisfies, errors.IsNotFound)
c.Assert(err, gc.ErrorMatches, `network "missing" not found`)
for i, test := range addNetworkErrorsTests {
c.Logf("test %d: %#v", i, test.args)
_, err := s.State.AddNetwork(test.args)
c.Check(err, gc.ErrorMatches, test.expectErr)
if strings.Contains(test.expectErr, "already exists") {
c.Check(err, jc.Satisfies, errors.IsAlreadyExists)
}
}
}
func (s *StateSuite) TestAllNetworks(c *gc.C) {
machine1, err := s.State.AddOneMachine(state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
IncludeNetworks: []string{"net1", "net2"},
ExcludeNetworks: []string{"net3", "net4"},
})
c.Assert(err, gc.IsNil)
machine2, err := s.State.AddOneMachine(state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
IncludeNetworks: []string{"net3", "net4"},
ExcludeNetworks: []string{"net1", "net2"},
})
c.Assert(err, gc.IsNil)
networks := []*state.Network{}
for i := 0; i < 4; i++ {
netName := fmt.Sprintf("net%d", i+1)
cidr := fmt.Sprintf("0.1.%d.0/24", i)
ifaceName := fmt.Sprintf("eth%d", i%2)
macAddress := fmt.Sprintf("aa:bb:cc:dd:ee:f%d", i)
machine := machine1
if i >= 2 {
machine = machine2
}
network, _ := addNetworkAndInterface(
c, s.State, machine,
netName, "provider-"+netName, cidr, i, false,
macAddress, ifaceName)
networks = append(networks, network)
allNetworks, err := s.State.AllNetworks()
c.Assert(err, gc.IsNil)
c.Assert(allNetworks, gc.HasLen, len(networks))
c.Assert(allNetworks, jc.DeepEquals, networks)
}
}
func (s *StateSuite) TestAddService(c *gc.C) {
charm := s.AddTestingCharm(c, "dummy")
_, err := s.State.AddService("haha/borken", "user-admin", charm, nil, nil)
c.Assert(err, gc.ErrorMatches, `cannot add service "haha/borken": invalid name`)
_, err = s.State.Service("haha/borken")
c.Assert(err, gc.ErrorMatches, `"haha/borken" is not a valid service name`)
// set that a nil charm is handled correctly
_, err = s.State.AddService("umadbro", "user-admin", nil, nil, nil)
c.Assert(err, gc.ErrorMatches, `cannot add service "umadbro": charm is nil`)
wordpress, err := s.State.AddService("wordpress", "user-admin", charm, nil, nil)
c.Assert(err, gc.IsNil)
c.Assert(wordpress.Name(), gc.Equals, "wordpress")
mysql, err := s.State.AddService("mysql", "user-admin", charm, nil, nil)
c.Assert(err, gc.IsNil)
c.Assert(mysql.Name(), gc.Equals, "mysql")
// Check that retrieving the new created services works correctly.
wordpress, err = s.State.Service("wordpress")
c.Assert(err, gc.IsNil)
c.Assert(wordpress.Name(), gc.Equals, "wordpress")
ch, _, err := wordpress.Charm()
c.Assert(err, gc.IsNil)
c.Assert(ch.URL(), gc.DeepEquals, charm.URL())
mysql, err = s.State.Service("mysql")
c.Assert(err, gc.IsNil)
c.Assert(mysql.Name(), gc.Equals, "mysql")
ch, _, err = mysql.Charm()
c.Assert(err, gc.IsNil)
c.Assert(ch.URL(), gc.DeepEquals, charm.URL())
}
func (s *StateSuite) TestAddServiceEnvironmentDying(c *gc.C) {
charm := s.AddTestingCharm(c, "dummy")
s.AddTestingService(c, "s0", charm)
// Check that services cannot be added if the environment is initially Dying.
env, err := s.State.Environment()
c.Assert(err, gc.IsNil)
err = env.Destroy()
c.Assert(err, gc.IsNil)
_, err = s.State.AddService("s1", "user-admin", charm, nil, nil)
c.Assert(err, gc.ErrorMatches, `cannot add service "s1": environment is no longer alive`)
}
func (s *StateSuite) TestAddServiceEnvironmentDyingAfterInitial(c *gc.C) {
charm := s.AddTestingCharm(c, "dummy")
s.AddTestingService(c, "s0", charm)
env, err := s.State.Environment()
c.Assert(err, gc.IsNil)
// Check that services cannot be added if the environment is initially
// Alive but set to Dying immediately before the transaction is run.
defer state.SetBeforeHooks(c, s.State, func() {
c.Assert(env.Life(), gc.Equals, state.Alive)
c.Assert(env.Destroy(), gc.IsNil)
}).Check()
_, err = s.State.AddService("s1", "user-admin", charm, nil, nil)
c.Assert(err, gc.ErrorMatches, `cannot add service "s1": environment is no longer alive`)
}
func (s *StateSuite) TestServiceNotFound(c *gc.C) {
_, err := s.State.Service("bummer")
c.Assert(err, gc.ErrorMatches, `service "bummer" not found`)
c.Assert(err, jc.Satisfies, errors.IsNotFound)
}
func (s *StateSuite) TestAddServiceNoTag(c *gc.C) {
charm := s.AddTestingCharm(c, "dummy")
_, err := s.State.AddService("wordpress", state.AdminUser, charm, nil, nil)
c.Assert(err, gc.ErrorMatches, "cannot add service \"wordpress\": Invalid ownertag admin")
}
func (s *StateSuite) TestAddServiceNotUserTag(c *gc.C) {
charm := s.AddTestingCharm(c, "dummy")
_, err := s.State.AddService("wordpress", "machine-3", charm, nil, nil)
c.Assert(err, gc.ErrorMatches, "cannot add service \"wordpress\": Invalid ownertag machine-3")
}
func (s *StateSuite) TestAddServiceNonExistentUser(c *gc.C) {
charm := s.AddTestingCharm(c, "dummy")
_, err := s.State.AddService("wordpress", "user-notAuser", charm, nil, nil)
c.Assert(err, gc.ErrorMatches, "cannot add service \"wordpress\": user notAuser doesn't exist")
}
func (s *StateSuite) TestAllServices(c *gc.C) {
charm := s.AddTestingCharm(c, "dummy")
services, err := s.State.AllServices()
c.Assert(err, gc.IsNil)
c.Assert(len(services), gc.Equals, 0)
// Check that after adding services the result is ok.
_, err = s.State.AddService("wordpress", "user-admin", charm, nil, nil)
c.Assert(err, gc.IsNil)
services, err = s.State.AllServices()
c.Assert(err, gc.IsNil)
c.Assert(len(services), gc.Equals, 1)
_, err = s.State.AddService("mysql", "user-admin", charm, nil, nil)
c.Assert(err, gc.IsNil)
services, err = s.State.AllServices()
c.Assert(err, gc.IsNil)
c.Assert(len(services), gc.Equals, 2)
// Check the returned service, order is defined by sorted keys.
c.Assert(services[0].Name(), gc.Equals, "wordpress")
c.Assert(services[1].Name(), gc.Equals, "mysql")
}
var inferEndpointsTests = []struct {
summary string
inputs [][]string
eps []state.Endpoint
err string
}{
{
summary: "insane args",
inputs: [][]string{nil},
err: `cannot relate 0 endpoints`,
}, {
summary: "insane args",
inputs: [][]string{{"blah", "blur", "bleurgh"}},
err: `cannot relate 3 endpoints`,
}, {
summary: "invalid args",
inputs: [][]string{
{"ping:"},
{":pong"},
{":"},
},
err: `invalid endpoint ".*"`,
}, {
summary: "unknown service",
inputs: [][]string{{"wooble"}},
err: `service "wooble" not found`,
}, {
summary: "invalid relations",
inputs: [][]string{
{"lg", "lg"},
{"ms", "ms"},
{"wp", "wp"},
{"rk1", "rk1"},
{"rk1", "rk2"},
},
err: `no relations found`,
}, {
summary: "valid peer relation",
inputs: [][]string{
{"rk1"},
{"rk1:ring"},
},
eps: []state.Endpoint{{
ServiceName: "rk1",
Relation: charm.Relation{
Name: "ring",
Interface: "riak",
Limit: 1,
Role: charm.RolePeer,
Scope: charm.ScopeGlobal,
},
}},
}, {
summary: "ambiguous provider/requirer relation",
inputs: [][]string{
{"ms", "wp"},
{"ms", "wp:db"},
},
err: `ambiguous relation: ".*" could refer to "wp:db ms:dev"; "wp:db ms:prod"`,
}, {
summary: "unambiguous provider/requirer relation",
inputs: [][]string{
{"ms:dev", "wp"},
{"ms:dev", "wp:db"},
},
eps: []state.Endpoint{{
ServiceName: "ms",
Relation: charm.Relation{
Interface: "mysql",
Name: "dev",
Role: charm.RoleProvider,
Scope: charm.ScopeGlobal,
Limit: 2,
},
}, {
ServiceName: "wp",
Relation: charm.Relation{
Interface: "mysql",
Name: "db",
Role: charm.RoleRequirer,
Scope: charm.ScopeGlobal,
Limit: 1,
},
}},
}, {
summary: "explicit logging relation is preferred over implicit juju-info",
inputs: [][]string{{"lg", "wp"}},
eps: []state.Endpoint{{
ServiceName: "lg",
Relation: charm.Relation{
Interface: "logging",
Name: "logging-directory",
Role: charm.RoleRequirer,
Scope: charm.ScopeContainer,
Limit: 1,
},
}, {
ServiceName: "wp",
Relation: charm.Relation{
Interface: "logging",
Name: "logging-dir",
Role: charm.RoleProvider,
Scope: charm.ScopeContainer,
},
}},
}, {
summary: "implict relations can be chosen explicitly",
inputs: [][]string{
{"lg:info", "wp"},
{"lg", "wp:juju-info"},
{"lg:info", "wp:juju-info"},
},
eps: []state.Endpoint{{
ServiceName: "lg",
Relation: charm.Relation{
Interface: "juju-info",
Name: "info",
Role: charm.RoleRequirer,
Scope: charm.ScopeContainer,
Limit: 1,
},
}, {
ServiceName: "wp",
Relation: charm.Relation{
Interface: "juju-info",
Name: "juju-info",
Role: charm.RoleProvider,
Scope: charm.ScopeGlobal,
},
}},
}, {
summary: "implicit relations will be chosen if there are no other options",
inputs: [][]string{{"lg", "ms"}},
eps: []state.Endpoint{{
ServiceName: "lg",
Relation: charm.Relation{
Interface: "juju-info",
Name: "info",
Role: charm.RoleRequirer,
Scope: charm.ScopeContainer,
Limit: 1,
},
}, {
ServiceName: "ms",
Relation: charm.Relation{
Interface: "juju-info",
Name: "juju-info",
Role: charm.RoleProvider,
Scope: charm.ScopeGlobal,
},
}},
},
}
func (s *StateSuite) TestInferEndpoints(c *gc.C) {
s.AddTestingService(c, "ms", s.AddTestingCharm(c, "mysql-alternative"))
s.AddTestingService(c, "wp", s.AddTestingCharm(c, "wordpress"))
s.AddTestingService(c, "lg", s.AddTestingCharm(c, "logging"))
riak := s.AddTestingCharm(c, "riak")
s.AddTestingService(c, "rk1", riak)
s.AddTestingService(c, "rk2", riak)
for i, t := range inferEndpointsTests {
c.Logf("test %d", i)
for j, input := range t.inputs {
c.Logf(" input %d", j)
eps, err := s.State.InferEndpoints(input)
if t.err == "" {
c.Assert(err, gc.IsNil)
c.Assert(eps, gc.DeepEquals, t.eps)
} else {
c.Assert(err, gc.ErrorMatches, t.err)
}
}
}
}
func (s *StateSuite) TestEnvironConfig(c *gc.C) {
attrs := map[string]interface{}{
"authorized-keys": "different-keys",
"arbitrary-key": "shazam!",
}
cfg, err := s.State.EnvironConfig()
c.Assert(err, gc.IsNil)
err = s.State.UpdateEnvironConfig(attrs, nil, nil)
c.Assert(err, gc.IsNil)
cfg, err = cfg.Apply(attrs)
c.Assert(err, gc.IsNil)
oldCfg, err := s.State.EnvironConfig()
c.Assert(err, gc.IsNil)
c.Assert(oldCfg, gc.DeepEquals, cfg)
}
func (s *StateSuite) TestEnvironConstraints(c *gc.C) {
// Environ constraints start out empty (for now).
cons, err := s.State.EnvironConstraints()
c.Assert(err, gc.IsNil)
c.Assert(&cons, jc.Satisfies, constraints.IsEmpty)
// Environ constraints can be set.
cons2 := constraints.Value{Mem: uint64p(1024)}
err = s.State.SetEnvironConstraints(cons2)
c.Assert(err, gc.IsNil)
cons3, err := s.State.EnvironConstraints()
c.Assert(err, gc.IsNil)
c.Assert(cons3, gc.DeepEquals, cons2)
// Environ constraints are completely overwritten when re-set.
cons4 := constraints.Value{CpuPower: uint64p(250)}
err = s.State.SetEnvironConstraints(cons4)
c.Assert(err, gc.IsNil)
cons5, err := s.State.EnvironConstraints()
c.Assert(err, gc.IsNil)
c.Assert(cons5, gc.DeepEquals, cons4)
}
func (s *StateSuite) TestSetInvalidConstraints(c *gc.C) {
cons := constraints.MustParse("mem=4G instance-type=foo")
err := s.State.SetEnvironConstraints(cons)
c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "mem" overlaps with "instance-type"`)
}
func (s *StateSuite) TestSetUnsupportedConstraintsWarning(c *gc.C) {
defer loggo.ResetWriters()
logger := loggo.GetLogger("test")
logger.SetLogLevel(loggo.DEBUG)
tw := &loggo.TestWriter{}
c.Assert(loggo.RegisterWriter("constraints-tester", tw, loggo.DEBUG), gc.IsNil)
cons := constraints.MustParse("mem=4G cpu-power=10")
err := s.State.SetEnvironConstraints(cons)
c.Assert(err, gc.IsNil)
c.Assert(tw.Log, jc.LogMatches, jc.SimpleMessages{{
loggo.WARNING,
`setting environment constraints: unsupported constraints: cpu-power`},
})
econs, err := s.State.EnvironConstraints()
c.Assert(err, gc.IsNil)
c.Assert(econs, gc.DeepEquals, cons)
}
func (s *StateSuite) TestWatchServicesBulkEvents(c *gc.C) {
// Alive service...
dummyCharm := s.AddTestingCharm(c, "dummy")
alive := s.AddTestingService(c, "service0", dummyCharm)
// Dying service...
dying := s.AddTestingService(c, "service1", dummyCharm)
keepDying, err := dying.AddUnit()
c.Assert(err, gc.IsNil)
err = dying.Destroy()
c.Assert(err, gc.IsNil)
// Dead service (actually, gone, Dead == removed in this case).
gone := s.AddTestingService(c, "service2", dummyCharm)
err = gone.Destroy()
c.Assert(err, gc.IsNil)
// All except gone are reported in initial event.
w := s.State.WatchServices()
defer statetesting.AssertStop(c, w)
wc := statetesting.NewStringsWatcherC(c, s.State, w)
wc.AssertChange(alive.Name(), dying.Name())
wc.AssertNoChange()
// Remove them all; alive/dying changes reported.
err = alive.Destroy()
c.Assert(err, gc.IsNil)
err = keepDying.Destroy()
c.Assert(err, gc.IsNil)
wc.AssertChange(alive.Name(), dying.Name())
wc.AssertNoChange()
}
func (s *StateSuite) TestWatchServicesLifecycle(c *gc.C) {
// Initial event is empty when no services.
w := s.State.WatchServices()
defer statetesting.AssertStop(c, w)
wc := statetesting.NewStringsWatcherC(c, s.State, w)
wc.AssertChange()
wc.AssertNoChange()
// Add a service: reported.
service := s.AddTestingService(c, "service", s.AddTestingCharm(c, "dummy"))
wc.AssertChange("service")
wc.AssertNoChange()
// Change the service: not reported.
keepDying, err := service.AddUnit()
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
// Make it Dying: reported.
err = service.Destroy()
c.Assert(err, gc.IsNil)
wc.AssertChange("service")
wc.AssertNoChange()
// Make it Dead(/removed): reported.
err = keepDying.Destroy()
c.Assert(err, gc.IsNil)
wc.AssertChange("service")
wc.AssertNoChange()
}
func (s *StateSuite) TestWatchServicesDiesOnStateClose(c *gc.C) {
// This test is testing logic in watcher.lifecycleWatcher,
// which is also used by:
// Service.WatchUnits
// Service.WatchRelations
// State.WatchEnviron
// Machine.WatchContainers
testWatcherDiesWhenStateCloses(c, func(c *gc.C, st *state.State) waiter {
w := st.WatchServices()
<-w.Changes()
return w
})
}
func (s *StateSuite) TestWatchMachinesBulkEvents(c *gc.C) {
// Alive machine...
alive, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
// Dying machine...
dying, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
err = dying.SetProvisioned(instance.Id("i-blah"), "fake-nonce", nil)
c.Assert(err, gc.IsNil)
err = dying.Destroy()
c.Assert(err, gc.IsNil)
// Dead machine...
dead, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
err = dead.EnsureDead()
c.Assert(err, gc.IsNil)
// Gone machine.
gone, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
err = gone.EnsureDead()
c.Assert(err, gc.IsNil)
err = gone.Remove()
c.Assert(err, gc.IsNil)
// All except gone machine are reported in initial event.
w := s.State.WatchEnvironMachines()
defer statetesting.AssertStop(c, w)
wc := statetesting.NewStringsWatcherC(c, s.State, w)
wc.AssertChange(alive.Id(), dying.Id(), dead.Id())
wc.AssertNoChange()
// Remove them all; alive/dying changes reported; dead never mentioned again.
err = alive.Destroy()
c.Assert(err, gc.IsNil)
err = dying.EnsureDead()
c.Assert(err, gc.IsNil)
err = dying.Remove()
c.Assert(err, gc.IsNil)
err = dead.Remove()
c.Assert(err, gc.IsNil)
wc.AssertChange(alive.Id(), dying.Id())
wc.AssertNoChange()
}
func (s *StateSuite) TestWatchMachinesLifecycle(c *gc.C) {
// Initial event is empty when no machines.
w := s.State.WatchEnvironMachines()
defer statetesting.AssertStop(c, w)
wc := statetesting.NewStringsWatcherC(c, s.State, w)
wc.AssertChange()
wc.AssertNoChange()
// Add a machine: reported.
machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
wc.AssertChange("0")
wc.AssertNoChange()
// Change the machine: not reported.
err = machine.SetProvisioned(instance.Id("i-blah"), "fake-nonce", nil)
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
// Make it Dying: reported.
err = machine.Destroy()
c.Assert(err, gc.IsNil)
wc.AssertChange("0")
wc.AssertNoChange()
// Make it Dead: reported.
err = machine.EnsureDead()
c.Assert(err, gc.IsNil)
wc.AssertChange("0")
wc.AssertNoChange()
// Remove it: not reported.
err = machine.Remove()
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
}
func (s *StateSuite) TestWatchMachinesIncludesOldMachines(c *gc.C) {
// Older versions of juju do not write the "containertype" field.
// This has caused machines to not be detected in the initial event.
machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
err = s.machines.Update(
bson.D{{"_id", machine.Id()}},
bson.D{{"$unset", bson.D{{"containertype", 1}}}},
)
c.Assert(err, gc.IsNil)
w := s.State.WatchEnvironMachines()
defer statetesting.AssertStop(c, w)
wc := statetesting.NewStringsWatcherC(c, s.State, w)
wc.AssertChange(machine.Id())
wc.AssertNoChange()
}
func (s *StateSuite) TestWatchMachinesIgnoresContainers(c *gc.C) {
// Initial event is empty when no machines.
w := s.State.WatchEnvironMachines()
defer statetesting.AssertStop(c, w)
wc := statetesting.NewStringsWatcherC(c, s.State, w)
wc.AssertChange()
wc.AssertNoChange()
// Add a machine: reported.
template := state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}
machines, err := s.State.AddMachines(template)
c.Assert(err, gc.IsNil)
c.Assert(machines, gc.HasLen, 1)
machine := machines[0]
wc.AssertChange("0")
wc.AssertNoChange()
// Add a container: not reported.
m, err := s.State.AddMachineInsideMachine(template, machine.Id(), instance.LXC)
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
// Make the container Dying: not reported.
err = m.Destroy()
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
// Make the container Dead: not reported.
err = m.EnsureDead()
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
// Remove the container: not reported.
err = m.Remove()
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
}
func (s *StateSuite) TestWatchContainerLifecycle(c *gc.C) {
// Add a host machine.
template := state.MachineTemplate{
Series: "quantal",
Jobs: []state.MachineJob{state.JobHostUnits},
}
machine, err := s.State.AddOneMachine(template)
c.Assert(err, gc.IsNil)
otherMachine, err := s.State.AddOneMachine(template)
c.Assert(err, gc.IsNil)
// Initial event is empty when no containers.
w := machine.WatchContainers(instance.LXC)
defer statetesting.AssertStop(c, w)
wAll := machine.WatchAllContainers()
defer statetesting.AssertStop(c, wAll)
wc := statetesting.NewStringsWatcherC(c, s.State, w)
wc.AssertChange()
wc.AssertNoChange()
wcAll := statetesting.NewStringsWatcherC(c, s.State, wAll)
wcAll.AssertChange()
wcAll.AssertNoChange()
// Add a container of the required type: reported.
m, err := s.State.AddMachineInsideMachine(template, machine.Id(), instance.LXC)
c.Assert(err, gc.IsNil)
wc.AssertChange("0/lxc/0")
wc.AssertNoChange()
wcAll.AssertChange("0/lxc/0")
wcAll.AssertNoChange()
// Add a container of a different type: not reported.
m1, err := s.State.AddMachineInsideMachine(template, machine.Id(), instance.KVM)
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
// But reported by the all watcher.
wcAll.AssertChange("0/kvm/0")
wcAll.AssertNoChange()
// Add a nested container of the right type: not reported.
mchild, err := s.State.AddMachineInsideMachine(template, m.Id(), instance.LXC)
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
wcAll.AssertNoChange()
// Add a container of a different machine: not reported.
m2, err := s.State.AddMachineInsideMachine(template, otherMachine.Id(), instance.LXC)
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
statetesting.AssertStop(c, w)
wcAll.AssertNoChange()
statetesting.AssertStop(c, wAll)
w = machine.WatchContainers(instance.LXC)
defer statetesting.AssertStop(c, w)
wc = statetesting.NewStringsWatcherC(c, s.State, w)
wAll = machine.WatchAllContainers()
defer statetesting.AssertStop(c, wAll)
wcAll = statetesting.NewStringsWatcherC(c, s.State, wAll)
wc.AssertChange("0/lxc/0")
wc.AssertNoChange()
wcAll.AssertChange("0/kvm/0", "0/lxc/0")
wcAll.AssertNoChange()
// Make the container Dying: cannot because of nested container.
err = m.Destroy()
c.Assert(err, gc.ErrorMatches, `machine .* is hosting containers ".*"`)
err = mchild.EnsureDead()
c.Assert(err, gc.IsNil)
err = mchild.Remove()
c.Assert(err, gc.IsNil)
// Make the container Dying: reported.
err = m.Destroy()
c.Assert(err, gc.IsNil)
wc.AssertChange("0/lxc/0")
wc.AssertNoChange()
wcAll.AssertChange("0/lxc/0")
wcAll.AssertNoChange()
// Make the other containers Dying: not reported.
err = m1.Destroy()
c.Assert(err, gc.IsNil)
err = m2.Destroy()
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
// But reported by the all watcher.
wcAll.AssertChange("0/kvm/0")
wcAll.AssertNoChange()
// Make the container Dead: reported.
err = m.EnsureDead()
c.Assert(err, gc.IsNil)
wc.AssertChange("0/lxc/0")
wc.AssertNoChange()
wcAll.AssertChange("0/lxc/0")
wcAll.AssertNoChange()
// Make the other containers Dead: not reported.
err = m1.EnsureDead()
c.Assert(err, gc.IsNil)
err = m2.EnsureDead()
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
// But reported by the all watcher.
wcAll.AssertChange("0/kvm/0")
wcAll.AssertNoChange()
// Remove the container: not reported.
err = m.Remove()
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
wcAll.AssertNoChange()
}
func (s *StateSuite) TestWatchMachineHardwareCharacteristics(c *gc.C) {
// Add a machine: reported.
machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
w := machine.WatchHardwareCharacteristics()
defer statetesting.AssertStop(c, w)
// Initial event.
wc := statetesting.NewNotifyWatcherC(c, s.State, w)
wc.AssertOneChange()
// Provision a machine: reported.
err = machine.SetProvisioned(instance.Id("i-blah"), "fake-nonce", nil)
c.Assert(err, gc.IsNil)
wc.AssertOneChange()
// Alter the machine: not reported.
vers := version.MustParseBinary("1.2.3-gutsy-ppc")
err = machine.SetAgentVersion(vers)
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
}
func (s *StateSuite) TestWatchStateServerInfo(c *gc.C) {
_, err := s.State.AddMachine("quantal", state.JobManageEnviron)
c.Assert(err, gc.IsNil)
w := s.State.WatchStateServerInfo()
defer statetesting.AssertStop(c, w)
// Initial event.
wc := statetesting.NewNotifyWatcherC(c, s.State, w)
wc.AssertOneChange()
info, err := s.State.StateServerInfo()
c.Assert(err, gc.IsNil)
c.Assert(info, jc.DeepEquals, &state.StateServerInfo{
MachineIds: []string{"0"},
VotingMachineIds: []string{"0"},
})
s.PatchValue(state.StateServerAvailable, func(m *state.Machine) (bool, error) {
return true, nil
})
err = s.State.EnsureAvailability(3, constraints.Value{}, "quantal")
c.Assert(err, gc.IsNil)
wc.AssertOneChange()
info, err = s.State.StateServerInfo()
c.Assert(err, gc.IsNil)
c.Assert(info, jc.DeepEquals, &state.StateServerInfo{
MachineIds: []string{"0", "1", "2"},
VotingMachineIds: []string{"0", "1", "2"},
})
}
func (s *StateSuite) TestAdditionalValidation(c *gc.C) {
updateAttrs := map[string]interface{}{"logging-config": "juju=ERROR"}
configValidator1 := func(updateAttrs map[string]interface{}, removeAttrs []string, oldConfig *config.Config) error {
c.Assert(updateAttrs, gc.DeepEquals, map[string]interface{}{"logging-config": "juju=ERROR"})
if _, found := updateAttrs["logging-config"]; found {
return fmt.Errorf("cannot change logging-config")
}
return nil
}
removeAttrs := []string{"logging-config"}
configValidator2 := func(updateAttrs map[string]interface{}, removeAttrs []string, oldConfig *config.Config) error {
c.Assert(removeAttrs, gc.DeepEquals, []string{"logging-config"})
for _, i := range removeAttrs {
if i == "logging-config" {
return fmt.Errorf("cannot remove logging-config")
}
}
return nil
}
configValidator3 := func(updateAttrs map[string]interface{}, removeAttrs []string, oldConfig *config.Config) error {
return nil
}
err := s.State.UpdateEnvironConfig(updateAttrs, nil, configValidator1)
c.Assert(err, gc.ErrorMatches, "cannot change logging-config")
err = s.State.UpdateEnvironConfig(nil, removeAttrs, configValidator2)
c.Assert(err, gc.ErrorMatches, "cannot remove logging-config")
err = s.State.UpdateEnvironConfig(updateAttrs, nil, configValidator3)
c.Assert(err, gc.IsNil)
}
type attrs map[string]interface{}
func (s *StateSuite) TestWatchEnvironConfig(c *gc.C) {
w := s.State.WatchEnvironConfig()
defer statetesting.AssertStop(c, w)
// TODO(fwereade) just use a NotifyWatcher and NotifyWatcherC to test it.
assertNoChange := func() {
s.State.StartSync()
select {
case got := <-w.Changes():
c.Fatalf("got unexpected change: %#v", got)
case <-time.After(testing.ShortWait):
}
}
assertChange := func(change attrs) {
cfg, err := s.State.EnvironConfig()
c.Assert(err, gc.IsNil)
cfg, err = cfg.Apply(change)
c.Assert(err, gc.IsNil)
if change != nil {
err = s.State.UpdateEnvironConfig(change, nil, nil)
c.Assert(err, gc.IsNil)
}
s.State.StartSync()
select {
case got, ok := <-w.Changes():
c.Assert(ok, gc.Equals, true)
c.Assert(got.AllAttrs(), gc.DeepEquals, cfg.AllAttrs())
case <-time.After(testing.LongWait):
c.Fatalf("did not get change: %#v", change)
}
assertNoChange()
}
assertChange(nil)
assertChange(attrs{"default-series": "another-series"})
assertChange(attrs{"fancy-new-key": "arbitrary-value"})
}
func (s *StateSuite) TestWatchEnvironConfigDiesOnStateClose(c *gc.C) {
testWatcherDiesWhenStateCloses(c, func(c *gc.C, st *state.State) waiter {
w := st.WatchEnvironConfig()
<-w.Changes()
return w
})
}
func (s *StateSuite) TestWatchForEnvironConfigChanges(c *gc.C) {
cur := version.Current.Number
err := statetesting.SetAgentVersion(s.State, cur)
c.Assert(err, gc.IsNil)
w := s.State.WatchForEnvironConfigChanges()
defer statetesting.AssertStop(c, w)
wc := statetesting.NewNotifyWatcherC(c, s.State, w)
// Initially we get one change notification
wc.AssertOneChange()
// Multiple changes will only result in a single change notification
newVersion := cur
newVersion.Minor += 1
err = statetesting.SetAgentVersion(s.State, newVersion)
c.Assert(err, gc.IsNil)
newerVersion := newVersion
newerVersion.Minor += 1
err = statetesting.SetAgentVersion(s.State, newerVersion)
c.Assert(err, gc.IsNil)
wc.AssertOneChange()
// Setting it to the same value does not trigger a change notification
err = statetesting.SetAgentVersion(s.State, newerVersion)
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
}
func (s *StateSuite) TestWatchEnvironConfigCorruptConfig(c *gc.C) {
cfg, err := s.State.EnvironConfig()
c.Assert(err, gc.IsNil)
// Corrupt the environment configuration.
settings := s.Session.DB("juju").C("settings")
err = settings.UpdateId("e", bson.D{{"$unset", bson.D{{"name", 1}}}})
c.Assert(err, gc.IsNil)
s.State.StartSync()
// Start watching the configuration.
watcher := s.State.WatchEnvironConfig()
defer watcher.Stop()
done := make(chan *config.Config)
go func() {
select {
case cfg, ok := <-watcher.Changes():
if !ok {
c.Errorf("watcher channel closed")
} else {
done <- cfg
}
case <-time.After(5 * time.Second):
c.Fatalf("no environment configuration observed")
}
}()
s.State.StartSync()
// The invalid configuration must not have been generated.
select {
case <-done:
c.Fatalf("configuration returned too soon")
case <-time.After(testing.ShortWait):
}
// Fix the configuration.
err = settings.UpdateId("e", bson.D{{"$set", bson.D{{"name", "foo"}}}})
c.Assert(err, gc.IsNil)
fixed := cfg.AllAttrs()
err = s.State.UpdateEnvironConfig(fixed, nil, nil)
c.Assert(err, gc.IsNil)
s.State.StartSync()
select {
case got := <-done:
c.Assert(got.AllAttrs(), gc.DeepEquals, fixed)
case <-time.After(5 * time.Second):
c.Fatalf("no environment configuration observed")
}
}
func (s *StateSuite) TestAddAndGetEquivalence(c *gc.C) {
// The equivalence tested here isn't necessarily correct, and
// comparing private details is discouraged in the project.
// The implementation might choose to cache information, or
// to have different logic when adding or removing, and the
// comparison might fail despite it being correct.
// That said, we've had bugs with txn-revno being incorrect
// before, so this testing at least ensures we're conscious
// about such changes.
m1, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
m2, err := s.State.Machine(m1.Id())
c.Assert(m1, jc.DeepEquals, m2)
charm1 := s.AddTestingCharm(c, "wordpress")
charm2, err := s.State.Charm(charm1.URL())
c.Assert(err, gc.IsNil)
c.Assert(charm1, jc.DeepEquals, charm2)
wordpress1 := s.AddTestingService(c, "wordpress", charm1)
wordpress2, err := s.State.Service("wordpress")
c.Assert(err, gc.IsNil)
c.Assert(wordpress1, jc.DeepEquals, wordpress2)
unit1, err := wordpress1.AddUnit()
c.Assert(err, gc.IsNil)
unit2, err := s.State.Unit("wordpress/0")
c.Assert(err, gc.IsNil)
c.Assert(unit1, jc.DeepEquals, unit2)
s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql"))
c.Assert(err, gc.IsNil)
eps, err := s.State.InferEndpoints([]string{"wordpress", "mysql"})
c.Assert(err, gc.IsNil)
relation1, err := s.State.AddRelation(eps...)
c.Assert(err, gc.IsNil)
relation2, err := s.State.EndpointsRelation(eps...)
c.Assert(relation1, jc.DeepEquals, relation2)
relation3, err := s.State.Relation(relation1.Id())
c.Assert(relation1, jc.DeepEquals, relation3)
}
func tryOpenState(info *state.Info) error {
st, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil))
if err == nil {
st.Close()
}
return err
}
func (s *StateSuite) TestOpenWithoutSetMongoPassword(c *gc.C) {
info := state.TestingStateInfo()
info.Tag, info.Password = "arble", "bar"
err := tryOpenState(info)
c.Assert(err, jc.Satisfies, errors.IsUnauthorized)
info.Tag, info.Password = "arble", ""
err = tryOpenState(info)
c.Assert(err, jc.Satisfies, errors.IsUnauthorized)
info.Tag, info.Password = "", ""
err = tryOpenState(info)
c.Assert(err, gc.IsNil)
}
func (s *StateSuite) TestOpenBadAddress(c *gc.C) {
info := state.TestingStateInfo()
info.Addrs = []string{"0.1.2.3:1234"}
st, err := state.Open(info, state.DialOpts{
Timeout: 1 * time.Millisecond,
}, state.Policy(nil))
if err == nil {
st.Close()
}
c.Assert(err, gc.ErrorMatches, "no reachable servers")
}
func (s *StateSuite) TestOpenDelaysRetryBadAddress(c *gc.C) {
// Default mgo retry delay
retryDelay := 500 * time.Millisecond
info := state.TestingStateInfo()
info.Addrs = []string{"0.1.2.3:1234"}
t0 := time.Now()
st, err := state.Open(info, state.DialOpts{
Timeout: 1 * time.Millisecond,
}, state.Policy(nil))
if err == nil {
st.Close()
}
c.Assert(err, gc.ErrorMatches, "no reachable servers")
// tryOpenState should have delayed for at least retryDelay
// internally mgo will try three times in a row before returning
// to the caller.
if t1 := time.Since(t0); t1 < 3*retryDelay {
c.Errorf("mgo.Dial only paused for %v, expected at least %v", t1, 3*retryDelay)
}
}
func testSetPassword(c *gc.C, getEntity func() (state.Authenticator, error)) {
e, err := getEntity()
c.Assert(err, gc.IsNil)
c.Assert(e.PasswordValid(goodPassword), gc.Equals, false)
err = e.SetPassword(goodPassword)
c.Assert(err, gc.IsNil)
c.Assert(e.PasswordValid(goodPassword), gc.Equals, true)
// Check a newly-fetched entity has the same password.
e2, err := getEntity()
c.Assert(err, gc.IsNil)
c.Assert(e2.PasswordValid(goodPassword), gc.Equals, true)
err = e.SetPassword(alternatePassword)
c.Assert(err, gc.IsNil)
c.Assert(e.PasswordValid(goodPassword), gc.Equals, false)
c.Assert(e.PasswordValid(alternatePassword), gc.Equals, true)
// Check that refreshing fetches the new password
err = e2.Refresh()
c.Assert(err, gc.IsNil)
c.Assert(e2.PasswordValid(alternatePassword), gc.Equals, true)
if le, ok := e.(lifer); ok {
testWhenDying(c, le, noErr, deadErr, func() error {
return e.SetPassword("arble-farble-dying-yarble")
})
}
}
func testSetAgentCompatPassword(c *gc.C, entity state.Authenticator) {
// In Juju versions 1.16 and older we used UserPasswordHash(password,CompatSalt)
// for Machine and Unit agents. This was determined to be overkill
// (since we know that Unit agents will actually use
// utils.RandomPassword() and get 18 bytes of entropy, and thus won't
// be brute-forced.)
c.Assert(entity.PasswordValid(goodPassword), jc.IsFalse)
agentHash := utils.AgentPasswordHash(goodPassword)
err := state.SetPasswordHash(entity, agentHash)
c.Assert(err, gc.IsNil)
c.Assert(entity.PasswordValid(goodPassword), jc.IsTrue)
c.Assert(entity.PasswordValid(alternatePassword), jc.IsFalse)
c.Assert(state.GetPasswordHash(entity), gc.Equals, agentHash)
backwardsCompatibleHash := utils.UserPasswordHash(goodPassword, utils.CompatSalt)
c.Assert(backwardsCompatibleHash, gc.Not(gc.Equals), agentHash)
err = state.SetPasswordHash(entity, backwardsCompatibleHash)
c.Assert(err, gc.IsNil)
c.Assert(entity.PasswordValid(alternatePassword), jc.IsFalse)
c.Assert(state.GetPasswordHash(entity), gc.Equals, backwardsCompatibleHash)
// After succeeding to log in with the old compatible hash, the db
// should be updated with the new hash
c.Assert(entity.PasswordValid(goodPassword), jc.IsTrue)
c.Assert(state.GetPasswordHash(entity), gc.Equals, agentHash)
c.Assert(entity.PasswordValid(goodPassword), jc.IsTrue)
// Agents are unable to set short passwords
err = entity.SetPassword("short")
c.Check(err, gc.ErrorMatches, "password is only 5 bytes long, and is not a valid Agent password")
// Grandfather clause. Agents that have short passwords are allowed if
// it was done in the compatHash form
agentHash = utils.AgentPasswordHash("short")
backwardsCompatibleHash = utils.UserPasswordHash("short", utils.CompatSalt)
err = state.SetPasswordHash(entity, backwardsCompatibleHash)
c.Assert(err, gc.IsNil)
c.Assert(entity.PasswordValid("short"), jc.IsTrue)
// We'll still update the hash, but now it points to the hash of the
// shorter password. Agents still can't set the password to it
c.Assert(state.GetPasswordHash(entity), gc.Equals, agentHash)
// Still valid with the shorter password
c.Assert(entity.PasswordValid("short"), jc.IsTrue)
}
type entity interface {
state.Entity
state.Lifer
state.Authenticator
state.MongoPassworder
}
func testSetMongoPassword(c *gc.C, getEntity func(st *state.State) (entity, error)) {
info := state.TestingStateInfo()
st, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil))
c.Assert(err, gc.IsNil)
defer st.Close()
// Turn on fully-authenticated mode.
err = st.SetAdminMongoPassword("admin-secret")
c.Assert(err, gc.IsNil)
// Set the password for the entity
ent, err := getEntity(st)
c.Assert(err, gc.IsNil)
err = ent.SetMongoPassword("foo")
c.Assert(err, gc.IsNil)
// Check that we cannot log in with the wrong password.
info.Tag = ent.Tag()
info.Password = "bar"
err = tryOpenState(info)
c.Assert(err, jc.Satisfies, errors.IsUnauthorized)
// Check that we can log in with the correct password.
info.Password = "foo"
st1, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil))
c.Assert(err, gc.IsNil)
defer st1.Close()
// Change the password with an entity derived from the newly
// opened and authenticated state.
ent, err = getEntity(st)
c.Assert(err, gc.IsNil)
err = ent.SetMongoPassword("bar")
c.Assert(err, gc.IsNil)
// Check that we cannot log in with the old password.
info.Password = "foo"
err = tryOpenState(info)
c.Assert(err, jc.Satisfies, errors.IsUnauthorized)
// Check that we can log in with the correct password.
info.Password = "bar"
err = tryOpenState(info)
c.Assert(err, gc.IsNil)
// Check that the administrator can still log in.
info.Tag, info.Password = "", "admin-secret"
err = tryOpenState(info)
c.Assert(err, gc.IsNil)
// Remove the admin password so that the test harness can reset the state.
err = st.SetAdminMongoPassword("")
c.Assert(err, gc.IsNil)
}
func (s *StateSuite) TestSetAdminMongoPassword(c *gc.C) {
// Check that we can SetAdminMongoPassword to nothing when there's
// no password currently set.
err := s.State.SetAdminMongoPassword("")
c.Assert(err, gc.IsNil)
err = s.State.SetAdminMongoPassword("foo")
c.Assert(err, gc.IsNil)
defer s.State.SetAdminMongoPassword("")
info := state.TestingStateInfo()
err = tryOpenState(info)
c.Assert(err, jc.Satisfies, errors.IsUnauthorized)
info.Password = "foo"
err = tryOpenState(info)
c.Assert(err, gc.IsNil)
err = s.State.SetAdminMongoPassword("")
c.Assert(err, gc.IsNil)
// Check that removing the password is idempotent.
err = s.State.SetAdminMongoPassword("")
c.Assert(err, gc.IsNil)
info.Password = ""
err = tryOpenState(info)
c.Assert(err, gc.IsNil)
}
type findEntityTest struct {
tag string
err string
}
var findEntityTests = []findEntityTest{{
tag: "",
err: `"" is not a valid tag`,
}, {
tag: "machine",
err: `"machine" is not a valid tag`,
}, {
tag: "-foo",
err: `"-foo" is not a valid tag`,
}, {
tag: "foo-",
err: `"foo-" is not a valid tag`,
}, {
tag: "---",
err: `"---" is not a valid tag`,
}, {
tag: "machine-bad",
err: `"machine-bad" is not a valid machine tag`,
}, {
tag: "unit-123",
err: `"unit-123" is not a valid unit tag`,
}, {
tag: "relation-blah",
err: `"relation-blah" is not a valid relation tag`,
}, {
tag: "relation-svc1.rel1#svc2.rel2",
err: `relation "svc1:rel1 svc2:rel2" not found`,
}, {
tag: "unit-foo",
err: `"unit-foo" is not a valid unit tag`,
}, {
tag: "service-",
err: `"service-" is not a valid service tag`,
}, {
tag: "service-foo/bar",
err: `"service-foo/bar" is not a valid service tag`,
}, {
tag: "environment-9f484882-2f18-4fd2-967d-db9663db7bea",
err: `environment "9f484882-2f18-4fd2-967d-db9663db7bea" not found`,
}, {
tag: "machine-1234",
err: `machine 1234 not found`,
}, {
tag: "unit-foo-654",
err: `unit "foo/654" not found`,
}, {
tag: "unit-foo-bar-654",
err: `unit "foo-bar/654" not found`,
}, {
tag: "machine-0",
}, {
tag: "service-ser-vice2",
}, {
tag: "relation-wordpress.db#ser-vice2.server",
}, {
tag: "unit-ser-vice2-0",
}, {
tag: "user-arble",
}, {
tag: "network-missing",
err: `network "missing" not found`,
}, {
tag: "network-",
err: `"network-" is not a valid network tag`,
}, {
tag: "network-net1",
}, {
// TODO(axw) 2013-12-04 #1257587
// remove backwards compatibility for environment-tag; see state.go
tag: "environment-notauuid",
//err: `"environment-notauuid" is not a valid environment tag`,
}, {
tag: "environment-testenv",
//err: `"environment-testenv" is not a valid environment tag`,
}}
var entityTypes = map[string]interface{}{
names.UserTagKind: (*state.User)(nil),
names.EnvironTagKind: (*state.Environment)(nil),
names.ServiceTagKind: (*state.Service)(nil),
names.UnitTagKind: (*state.Unit)(nil),
names.MachineTagKind: (*state.Machine)(nil),
names.RelationTagKind: (*state.Relation)(nil),
names.NetworkTagKind: (*state.Network)(nil),
}
func (s *StateSuite) TestFindEntity(c *gc.C) {
_, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
svc := s.AddTestingService(c, "ser-vice2", s.AddTestingCharm(c, "mysql"))
_, err = svc.AddUnit()
c.Assert(err, gc.IsNil)
_, err = s.State.AddUser("arble", "pass")
c.Assert(err, gc.IsNil)
s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
eps, err := s.State.InferEndpoints([]string{"wordpress", "ser-vice2"})
c.Assert(err, gc.IsNil)
rel, err := s.State.AddRelation(eps...)
c.Assert(err, gc.IsNil)
c.Assert(rel.String(), gc.Equals, "wordpress:db ser-vice2:server")
net1, err := s.State.AddNetwork(state.NetworkInfo{
Name: "net1",
ProviderId: "provider-id",
CIDR: "0.1.2.0/24",
VLANTag: 0,
})
c.Assert(err, gc.IsNil)
c.Assert(net1.Tag(), gc.Equals, "network-net1")
c.Assert(string(net1.ProviderId()), gc.Equals, "provider-id")
// environment tag is dynamically generated
env, err := s.State.Environment()
c.Assert(err, gc.IsNil)
findEntityTests = append([]findEntityTest{}, findEntityTests...)
findEntityTests = append(findEntityTests, findEntityTest{
tag: "environment-" + env.UUID(),
})
for i, test := range findEntityTests {
c.Logf("test %d: %q", i, test.tag)
e, err := s.State.FindEntity(test.tag)
if test.err != "" {
c.Assert(err, gc.ErrorMatches, test.err)
} else {
c.Assert(err, gc.IsNil)
kind, err := names.TagKind(test.tag)
c.Assert(err, gc.IsNil)
c.Assert(e, gc.FitsTypeOf, entityTypes[kind])
if kind == "environment" {
// TODO(axw) 2013-12-04 #1257587
// We *should* only be able to get the entity with its tag, but
// for backwards-compatibility we accept any non-UUID tag.
c.Assert(e.Tag(), gc.Equals, env.Tag())
} else {
c.Assert(e.Tag(), gc.Equals, test.tag)
}
}
}
}
func (s *StateSuite) TestParseTag(c *gc.C) {
bad := []string{
"",
"machine",
"-foo",
"foo-",
"---",
"foo-bar",
"unit-foo",
"network",
"network-",
}
for _, name := range bad {
c.Logf(name)
coll, id, err := state.ParseTag(s.State, name)
c.Check(coll, gc.Equals, "")
c.Check(id, gc.Equals, "")
c.Assert(err, gc.ErrorMatches, `".*" is not a valid( [a-z]+)? tag`)
}
// Parse a machine entity name.
m, err := s.State.AddMachine("quantal", state.JobHostUnits)
c.Assert(err, gc.IsNil)
coll, id, err := state.ParseTag(s.State, m.Tag())
c.Assert(coll, gc.Equals, "machines")
c.Assert(id, gc.Equals, m.Id())
c.Assert(err, gc.IsNil)
// Parse a service entity name.
svc := s.AddTestingService(c, "ser-vice2", s.AddTestingCharm(c, "dummy"))
coll, id, err = state.ParseTag(s.State, svc.Tag())
c.Assert(coll, gc.Equals, "services")
c.Assert(id, gc.Equals, svc.Name())
c.Assert(err, gc.IsNil)
// Parse a unit entity name.
u, err := svc.AddUnit()
c.Assert(err, gc.IsNil)
coll, id, err = state.ParseTag(s.State, u.Tag())
c.Assert(coll, gc.Equals, "units")
c.Assert(id, gc.Equals, u.Name())
c.Assert(err, gc.IsNil)
// Parse a user entity name.
user, err := s.State.AddUser("arble", "pass")
c.Assert(err, gc.IsNil)
coll, id, err = state.ParseTag(s.State, user.Tag())
c.Assert(coll, gc.Equals, "users")
c.Assert(id, gc.Equals, user.Name())
c.Assert(err, gc.IsNil)
// Parse an environment entity name.
env, err := s.State.Environment()
c.Assert(err, gc.IsNil)
coll, id, err = state.ParseTag(s.State, env.Tag())
c.Assert(coll, gc.Equals, "environments")
c.Assert(id, gc.Equals, env.UUID())
c.Assert(err, gc.IsNil)
// Parse a network name.
net1, err := s.State.AddNetwork(state.NetworkInfo{
Name: "net1",
ProviderId: "provider-id",
CIDR: "0.1.2.0/24",
VLANTag: 0,
})
c.Assert(err, gc.IsNil)
coll, id, err = state.ParseTag(s.State, net1.Tag())
c.Assert(coll, gc.Equals, "networks")
c.Assert(id, gc.Equals, net1.Name())
c.Assert(err, gc.IsNil)
}
func (s *StateSuite) TestWatchCleanups(c *gc.C) {
// Check initial event.
w := s.State.WatchCleanups()
defer statetesting.AssertStop(c, w)
wc := statetesting.NewNotifyWatcherC(c, s.State, w)
wc.AssertOneChange()
// Set up two relations for later use, check no events.
s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql"))
eps, err := s.State.InferEndpoints([]string{"wordpress", "mysql"})
c.Assert(err, gc.IsNil)
relM, err := s.State.AddRelation(eps...)
c.Assert(err, gc.IsNil)
s.AddTestingService(c, "varnish", s.AddTestingCharm(c, "varnish"))
c.Assert(err, gc.IsNil)
eps, err = s.State.InferEndpoints([]string{"wordpress", "varnish"})
c.Assert(err, gc.IsNil)
relV, err := s.State.AddRelation(eps...)
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
// Destroy one relation, check one change.
err = relM.Destroy()
c.Assert(err, gc.IsNil)
wc.AssertOneChange()
// Handle that cleanup doc and create another, check one change.
err = s.State.Cleanup()
c.Assert(err, gc.IsNil)
err = relV.Destroy()
c.Assert(err, gc.IsNil)
wc.AssertOneChange()
// Clean up final doc, check change.
err = s.State.Cleanup()
c.Assert(err, gc.IsNil)
wc.AssertOneChange()
// Stop watcher, check closed.
statetesting.AssertStop(c, w)
wc.AssertClosed()
}
func (s *StateSuite) TestWatchCleanupsDiesOnStateClose(c *gc.C) {
testWatcherDiesWhenStateCloses(c, func(c *gc.C, st *state.State) waiter {
w := st.WatchCleanups()
<-w.Changes()
return w
})
}
func (s *StateSuite) TestWatchCleanupsBulk(c *gc.C) {
// Check initial event.
w := s.State.WatchCleanups()
defer statetesting.AssertStop(c, w)
wc := statetesting.NewNotifyWatcherC(c, s.State, w)
wc.AssertOneChange()
// Create two peer relations by creating their services.
riak := s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak"))
_, err := riak.Endpoint("ring")
c.Assert(err, gc.IsNil)
allHooks := s.AddTestingService(c, "all-hooks", s.AddTestingCharm(c, "all-hooks"))
_, err = allHooks.Endpoint("self")
c.Assert(err, gc.IsNil)
wc.AssertNoChange()
// Destroy them both, check one change.
err = riak.Destroy()
c.Assert(err, gc.IsNil)
err = allHooks.Destroy()
c.Assert(err, gc.IsNil)
wc.AssertOneChange()
// Clean them both up, check one change.
err = s.State.Cleanup()
c.Assert(err, gc.IsNil)
wc.AssertOneChange()
}
func (s *StateSuite) TestWatchMinUnits(c *gc.C) {
// Check initial event.
w := s.State.WatchMinUnits()
defer statetesting.AssertStop(c, w)
wc := statetesting.NewStringsWatcherC(c, s.State, w)
wc.AssertChange()
wc.AssertNoChange()
// Set up services for later use.
wordpress := s.AddTestingService(c,
"wordpress", s.AddTestingCharm(c, "wordpress"))
mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql"))
wordpressName := wordpress.Name()