github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/cmd/juju/status_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"net/url"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/juju/charm"
    15  	charmtesting "github.com/juju/charm/testing"
    16  	"github.com/juju/cmd"
    17  	jc "github.com/juju/testing/checkers"
    18  	gc "launchpad.net/gocheck"
    19  	"launchpad.net/goyaml"
    20  
    21  	"github.com/juju/juju/cmd/envcmd"
    22  	"github.com/juju/juju/constraints"
    23  	"github.com/juju/juju/instance"
    24  	"github.com/juju/juju/juju"
    25  	"github.com/juju/juju/juju/testing"
    26  	"github.com/juju/juju/network"
    27  	"github.com/juju/juju/state"
    28  	"github.com/juju/juju/state/api/params"
    29  	"github.com/juju/juju/state/presence"
    30  	coretesting "github.com/juju/juju/testing"
    31  	"github.com/juju/juju/version"
    32  )
    33  
    34  func runStatus(c *gc.C, args ...string) (code int, stdout, stderr []byte) {
    35  	ctx := coretesting.Context(c)
    36  	code = cmd.Main(envcmd.Wrap(&StatusCommand{}), ctx, args)
    37  	stdout = ctx.Stdout.(*bytes.Buffer).Bytes()
    38  	stderr = ctx.Stderr.(*bytes.Buffer).Bytes()
    39  	return
    40  }
    41  
    42  type StatusSuite struct {
    43  	testing.JujuConnSuite
    44  }
    45  
    46  var _ = gc.Suite(&StatusSuite{})
    47  
    48  type M map[string]interface{}
    49  
    50  type L []interface{}
    51  
    52  type testCase struct {
    53  	summary string
    54  	steps   []stepper
    55  }
    56  
    57  func test(summary string, steps ...stepper) testCase {
    58  	return testCase{summary, steps}
    59  }
    60  
    61  type stepper interface {
    62  	step(c *gc.C, ctx *context)
    63  }
    64  
    65  type context struct {
    66  	st      *state.State
    67  	conn    *juju.Conn
    68  	charms  map[string]*state.Charm
    69  	pingers map[string]*presence.Pinger
    70  }
    71  
    72  func (s *StatusSuite) newContext() *context {
    73  	st := s.Conn.Environ.(testing.GetStater).GetStateInAPIServer()
    74  	// We make changes in the API server's state so that
    75  	// our changes to presence are immediately noticed
    76  	// in the status.
    77  	return &context{
    78  		st:      st,
    79  		conn:    s.Conn,
    80  		charms:  make(map[string]*state.Charm),
    81  		pingers: make(map[string]*presence.Pinger),
    82  	}
    83  }
    84  
    85  func (s *StatusSuite) resetContext(c *gc.C, ctx *context) {
    86  	for _, up := range ctx.pingers {
    87  		err := up.Kill()
    88  		c.Check(err, gc.IsNil)
    89  	}
    90  	s.JujuConnSuite.Reset(c)
    91  }
    92  
    93  func (ctx *context) run(c *gc.C, steps []stepper) {
    94  	for i, s := range steps {
    95  		c.Logf("step %d", i)
    96  		c.Logf("%#v", s)
    97  		s.step(c, ctx)
    98  	}
    99  }
   100  
   101  type aliver interface {
   102  	AgentAlive() (bool, error)
   103  	SetAgentAlive() (*presence.Pinger, error)
   104  	WaitAgentAlive(time.Duration) error
   105  }
   106  
   107  func (ctx *context) setAgentAlive(c *gc.C, a aliver) *presence.Pinger {
   108  	pinger, err := a.SetAgentAlive()
   109  	c.Assert(err, gc.IsNil)
   110  	ctx.st.StartSync()
   111  	err = a.WaitAgentAlive(coretesting.LongWait)
   112  	c.Assert(err, gc.IsNil)
   113  	agentAlive, err := a.AgentAlive()
   114  	c.Assert(err, gc.IsNil)
   115  	c.Assert(agentAlive, gc.Equals, true)
   116  	return pinger
   117  }
   118  
   119  // shortcuts for expected output.
   120  var (
   121  	machine0 = M{
   122  		"agent-state":                "started",
   123  		"dns-name":                   "dummyenv-0.dns",
   124  		"instance-id":                "dummyenv-0",
   125  		"series":                     "quantal",
   126  		"hardware":                   "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   127  		"state-server-member-status": "adding-vote",
   128  	}
   129  	machine1 = M{
   130  		"agent-state": "started",
   131  		"dns-name":    "dummyenv-1.dns",
   132  		"instance-id": "dummyenv-1",
   133  		"series":      "quantal",
   134  		"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   135  	}
   136  	machine2 = M{
   137  		"agent-state": "started",
   138  		"dns-name":    "dummyenv-2.dns",
   139  		"instance-id": "dummyenv-2",
   140  		"series":      "quantal",
   141  		"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   142  	}
   143  	machine3 = M{
   144  		"agent-state": "started",
   145  		"dns-name":    "dummyenv-3.dns",
   146  		"instance-id": "dummyenv-3",
   147  		"series":      "quantal",
   148  		"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   149  	}
   150  	machine4 = M{
   151  		"agent-state": "started",
   152  		"dns-name":    "dummyenv-4.dns",
   153  		"instance-id": "dummyenv-4",
   154  		"series":      "quantal",
   155  		"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   156  	}
   157  	machine1WithContainers = M{
   158  		"agent-state": "started",
   159  		"containers": M{
   160  			"1/lxc/0": M{
   161  				"agent-state": "started",
   162  				"containers": M{
   163  					"1/lxc/0/lxc/0": M{
   164  						"agent-state": "started",
   165  						"dns-name":    "dummyenv-3.dns",
   166  						"instance-id": "dummyenv-3",
   167  						"series":      "quantal",
   168  					},
   169  				},
   170  				"dns-name":    "dummyenv-2.dns",
   171  				"instance-id": "dummyenv-2",
   172  				"series":      "quantal",
   173  			},
   174  			"1/lxc/1": M{
   175  				"instance-id": "pending",
   176  				"series":      "quantal",
   177  			},
   178  		},
   179  		"dns-name":    "dummyenv-1.dns",
   180  		"instance-id": "dummyenv-1",
   181  		"series":      "quantal",
   182  		"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   183  	}
   184  	machine1WithContainersScoped = M{
   185  		"agent-state": "started",
   186  		"containers": M{
   187  			"1/lxc/0": M{
   188  				"agent-state": "started",
   189  				"dns-name":    "dummyenv-2.dns",
   190  				"instance-id": "dummyenv-2",
   191  				"series":      "quantal",
   192  			},
   193  		},
   194  		"dns-name":    "dummyenv-1.dns",
   195  		"instance-id": "dummyenv-1",
   196  		"series":      "quantal",
   197  		"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   198  	}
   199  	unexposedService = M{
   200  		"charm":   "cs:quantal/dummy-1",
   201  		"exposed": false,
   202  	}
   203  	exposedService = M{
   204  		"charm":   "cs:quantal/dummy-1",
   205  		"exposed": true,
   206  	}
   207  )
   208  
   209  type outputFormat struct {
   210  	name      string
   211  	marshal   func(v interface{}) ([]byte, error)
   212  	unmarshal func(data []byte, v interface{}) error
   213  }
   214  
   215  // statusFormats list all output formats supported by status command.
   216  var statusFormats = []outputFormat{
   217  	{"yaml", goyaml.Marshal, goyaml.Unmarshal},
   218  	{"json", json.Marshal, json.Unmarshal},
   219  }
   220  
   221  var machineCons = constraints.MustParse("cpu-cores=2 mem=8G root-disk=8G")
   222  
   223  var statusTests = []testCase{
   224  	// Status tests
   225  	test(
   226  		"bootstrap and starting a single instance",
   227  
   228  		// unlikely, as you can't run juju status in real life without
   229  		// machine/0 bootstrapped.
   230  		expect{
   231  			"empty state",
   232  			M{
   233  				"environment": "dummyenv",
   234  				"machines":    M{},
   235  				"services":    M{},
   236  			},
   237  		},
   238  
   239  		addMachine{machineId: "0", job: state.JobManageEnviron},
   240  		expect{
   241  			"simulate juju bootstrap by adding machine/0 to the state",
   242  			M{
   243  				"environment": "dummyenv",
   244  				"machines": M{
   245  					"0": M{
   246  						"instance-id":                "pending",
   247  						"series":                     "quantal",
   248  						"state-server-member-status": "adding-vote",
   249  					},
   250  				},
   251  				"services": M{},
   252  			},
   253  		},
   254  
   255  		startAliveMachine{"0"},
   256  		setAddresses{"0", []network.Address{
   257  			network.NewAddress("10.0.0.1", network.ScopeUnknown),
   258  			network.NewAddress("dummyenv-0.dns", network.ScopePublic),
   259  		}},
   260  		expect{
   261  			"simulate the PA starting an instance in response to the state change",
   262  			M{
   263  				"environment": "dummyenv",
   264  				"machines": M{
   265  					"0": M{
   266  						"agent-state":                "pending",
   267  						"dns-name":                   "dummyenv-0.dns",
   268  						"instance-id":                "dummyenv-0",
   269  						"series":                     "quantal",
   270  						"hardware":                   "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   271  						"state-server-member-status": "adding-vote",
   272  					},
   273  				},
   274  				"services": M{},
   275  			},
   276  		},
   277  
   278  		setMachineStatus{"0", params.StatusStarted, ""},
   279  		expect{
   280  			"simulate the MA started and set the machine status",
   281  			M{
   282  				"environment": "dummyenv",
   283  				"machines": M{
   284  					"0": machine0,
   285  				},
   286  				"services": M{},
   287  			},
   288  		},
   289  
   290  		setTools{"0", version.MustParseBinary("1.2.3-gutsy-ppc")},
   291  		expect{
   292  			"simulate the MA setting the version",
   293  			M{
   294  				"environment": "dummyenv",
   295  				"machines": M{
   296  					"0": M{
   297  						"dns-name":                   "dummyenv-0.dns",
   298  						"instance-id":                "dummyenv-0",
   299  						"agent-version":              "1.2.3",
   300  						"agent-state":                "started",
   301  						"series":                     "quantal",
   302  						"hardware":                   "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   303  						"state-server-member-status": "adding-vote",
   304  					},
   305  				},
   306  				"services": M{},
   307  			},
   308  		},
   309  	), test(
   310  		"deploy two services and two networks",
   311  		addMachine{machineId: "0", job: state.JobManageEnviron},
   312  		startAliveMachine{"0"},
   313  		setMachineStatus{"0", params.StatusStarted, ""},
   314  		setAddresses{"0", []network.Address{
   315  			network.NewAddress("10.0.0.1", network.ScopeUnknown),
   316  			network.NewAddress("dummyenv-0.dns", network.ScopePublic),
   317  		}},
   318  		addCharm{"dummy"},
   319  		addService{
   320  			name:     "networks-service",
   321  			charm:    "dummy",
   322  			networks: []string{"net1", "net2"},
   323  			cons:     constraints.MustParse("networks=foo,bar,^no,^good"),
   324  		},
   325  		addService{
   326  			name:  "no-networks-service",
   327  			charm: "dummy",
   328  			cons:  constraints.MustParse("networks=^mynet"),
   329  		},
   330  		addNetwork{
   331  			name:       "net1",
   332  			providerId: network.Id("provider-net1"),
   333  			cidr:       "0.1.2.0/24",
   334  			vlanTag:    0,
   335  		},
   336  		addNetwork{
   337  			name:       "net2",
   338  			providerId: network.Id("provider-vlan42"),
   339  			cidr:       "0.42.1.0/24",
   340  			vlanTag:    42,
   341  		},
   342  
   343  		expect{
   344  			"simulate just the two services and a bootstrap node",
   345  			M{
   346  				"environment": "dummyenv",
   347  				"machines": M{
   348  					"0": machine0,
   349  				},
   350  				"services": M{
   351  					"networks-service": M{
   352  						"charm":   "cs:quantal/dummy-1",
   353  						"exposed": false,
   354  						"networks": M{
   355  							"enabled":  L{"net1", "net2"},
   356  							"disabled": L{"foo", "bar", "no", "good"},
   357  						},
   358  					},
   359  					"no-networks-service": M{
   360  						"charm":   "cs:quantal/dummy-1",
   361  						"exposed": false,
   362  						"networks": M{
   363  							"disabled": L{"mynet"},
   364  						},
   365  					},
   366  				},
   367  				"networks": M{
   368  					"net1": M{
   369  						"provider-id": "provider-net1",
   370  						"cidr":        "0.1.2.0/24",
   371  					},
   372  					"net2": M{
   373  						"provider-id": "provider-vlan42",
   374  						"cidr":        "0.42.1.0/24",
   375  						"vlan-tag":    42,
   376  					},
   377  				},
   378  			},
   379  		},
   380  	), test(
   381  		"instance with different hardware characteristics",
   382  		addMachine{machineId: "0", cons: machineCons, job: state.JobManageEnviron},
   383  		setAddresses{"0", []network.Address{
   384  			network.NewAddress("10.0.0.1", network.ScopeUnknown),
   385  			network.NewAddress("dummyenv-0.dns", network.ScopePublic),
   386  		}},
   387  		startAliveMachine{"0"},
   388  		setMachineStatus{"0", params.StatusStarted, ""},
   389  		expect{
   390  			"machine 0 has specific hardware characteristics",
   391  			M{
   392  				"environment": "dummyenv",
   393  				"machines": M{
   394  					"0": M{
   395  						"agent-state":                "started",
   396  						"dns-name":                   "dummyenv-0.dns",
   397  						"instance-id":                "dummyenv-0",
   398  						"series":                     "quantal",
   399  						"hardware":                   "arch=amd64 cpu-cores=2 mem=8192M root-disk=8192M",
   400  						"state-server-member-status": "adding-vote",
   401  					},
   402  				},
   403  				"services": M{},
   404  			},
   405  		},
   406  	), test(
   407  		"instance without addresses",
   408  		addMachine{machineId: "0", cons: machineCons, job: state.JobManageEnviron},
   409  		startAliveMachine{"0"},
   410  		setMachineStatus{"0", params.StatusStarted, ""},
   411  		expect{
   412  			"machine 0 has no dns-name",
   413  			M{
   414  				"environment": "dummyenv",
   415  				"machines": M{
   416  					"0": M{
   417  						"agent-state":                "started",
   418  						"instance-id":                "dummyenv-0",
   419  						"series":                     "quantal",
   420  						"hardware":                   "arch=amd64 cpu-cores=2 mem=8192M root-disk=8192M",
   421  						"state-server-member-status": "adding-vote",
   422  					},
   423  				},
   424  				"services": M{},
   425  			},
   426  		},
   427  	), test(
   428  		"test pending and missing machines",
   429  		addMachine{machineId: "0", job: state.JobManageEnviron},
   430  		expect{
   431  			"machine 0 reports pending",
   432  			M{
   433  				"environment": "dummyenv",
   434  				"machines": M{
   435  					"0": M{
   436  						"instance-id":                "pending",
   437  						"series":                     "quantal",
   438  						"state-server-member-status": "adding-vote",
   439  					},
   440  				},
   441  				"services": M{},
   442  			},
   443  		},
   444  
   445  		startMissingMachine{"0"},
   446  		expect{
   447  			"machine 0 reports missing",
   448  			M{
   449  				"environment": "dummyenv",
   450  				"machines": M{
   451  					"0": M{
   452  						"instance-state":             "missing",
   453  						"instance-id":                "i-missing",
   454  						"agent-state":                "pending",
   455  						"series":                     "quantal",
   456  						"hardware":                   "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   457  						"state-server-member-status": "adding-vote",
   458  					},
   459  				},
   460  				"services": M{},
   461  			},
   462  		},
   463  	), test(
   464  		"add two services and expose one, then add 2 more machines and some units",
   465  		addMachine{machineId: "0", job: state.JobManageEnviron},
   466  		setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
   467  		startAliveMachine{"0"},
   468  		setMachineStatus{"0", params.StatusStarted, ""},
   469  		addCharm{"dummy"},
   470  		addService{name: "dummy-service", charm: "dummy"},
   471  		addService{name: "exposed-service", charm: "dummy"},
   472  		expect{
   473  			"no services exposed yet",
   474  			M{
   475  				"environment": "dummyenv",
   476  				"machines": M{
   477  					"0": machine0,
   478  				},
   479  				"services": M{
   480  					"dummy-service":   unexposedService,
   481  					"exposed-service": unexposedService,
   482  				},
   483  			},
   484  		},
   485  
   486  		setServiceExposed{"exposed-service", true},
   487  		expect{
   488  			"one exposed service",
   489  			M{
   490  				"environment": "dummyenv",
   491  				"machines": M{
   492  					"0": machine0,
   493  				},
   494  				"services": M{
   495  					"dummy-service":   unexposedService,
   496  					"exposed-service": exposedService,
   497  				},
   498  			},
   499  		},
   500  
   501  		addMachine{machineId: "1", job: state.JobHostUnits},
   502  		setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
   503  		startAliveMachine{"1"},
   504  		setMachineStatus{"1", params.StatusStarted, ""},
   505  		addMachine{machineId: "2", job: state.JobHostUnits},
   506  		setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
   507  		startAliveMachine{"2"},
   508  		setMachineStatus{"2", params.StatusStarted, ""},
   509  		expect{
   510  			"two more machines added",
   511  			M{
   512  				"environment": "dummyenv",
   513  				"machines": M{
   514  					"0": machine0,
   515  					"1": machine1,
   516  					"2": machine2,
   517  				},
   518  				"services": M{
   519  					"dummy-service":   unexposedService,
   520  					"exposed-service": exposedService,
   521  				},
   522  			},
   523  		},
   524  
   525  		addUnit{"dummy-service", "1"},
   526  		addAliveUnit{"exposed-service", "2"},
   527  		setUnitStatus{"exposed-service/0", params.StatusError, "You Require More Vespene Gas"},
   528  		// Open multiple ports with different protocols,
   529  		// ensure they're sorted on protocol, then number.
   530  		openUnitPort{"exposed-service/0", "udp", 10},
   531  		openUnitPort{"exposed-service/0", "udp", 2},
   532  		openUnitPort{"exposed-service/0", "tcp", 3},
   533  		openUnitPort{"exposed-service/0", "tcp", 2},
   534  		// Simulate some status with no info, while the agent is down.
   535  		setUnitStatus{"dummy-service/0", params.StatusStarted, ""},
   536  		expect{
   537  			"add two units, one alive (in error state), one down",
   538  			M{
   539  				"environment": "dummyenv",
   540  				"machines": M{
   541  					"0": machine0,
   542  					"1": machine1,
   543  					"2": machine2,
   544  				},
   545  				"services": M{
   546  					"exposed-service": M{
   547  						"charm":   "cs:quantal/dummy-1",
   548  						"exposed": true,
   549  						"units": M{
   550  							"exposed-service/0": M{
   551  								"machine":          "2",
   552  								"agent-state":      "error",
   553  								"agent-state-info": "You Require More Vespene Gas",
   554  								"open-ports": L{
   555  									"2/tcp", "3/tcp", "2/udp", "10/udp",
   556  								},
   557  								"public-address": "dummyenv-2.dns",
   558  							},
   559  						},
   560  					},
   561  					"dummy-service": M{
   562  						"charm":   "cs:quantal/dummy-1",
   563  						"exposed": false,
   564  						"units": M{
   565  							"dummy-service/0": M{
   566  								"machine":          "1",
   567  								"agent-state":      "down",
   568  								"agent-state-info": "(started)",
   569  								"public-address":   "dummyenv-1.dns",
   570  							},
   571  						},
   572  					},
   573  				},
   574  			},
   575  		},
   576  
   577  		addMachine{machineId: "3", job: state.JobHostUnits},
   578  		startMachine{"3"},
   579  		// Simulate some status with info, while the agent is down.
   580  		setAddresses{"3", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}},
   581  		setMachineStatus{"3", params.StatusStopped, "Really?"},
   582  		addMachine{machineId: "4", job: state.JobHostUnits},
   583  		setAddresses{"4", []network.Address{network.NewAddress("dummyenv-4.dns", network.ScopeUnknown)}},
   584  		startAliveMachine{"4"},
   585  		setMachineStatus{"4", params.StatusError, "Beware the red toys"},
   586  		ensureDyingUnit{"dummy-service/0"},
   587  		addMachine{machineId: "5", job: state.JobHostUnits},
   588  		ensureDeadMachine{"5"},
   589  		expect{
   590  			"add three more machine, one with a dead agent, one in error state and one dead itself; also one dying unit",
   591  			M{
   592  				"environment": "dummyenv",
   593  				"machines": M{
   594  					"0": machine0,
   595  					"1": machine1,
   596  					"2": machine2,
   597  					"3": M{
   598  						"dns-name":         "dummyenv-3.dns",
   599  						"instance-id":      "dummyenv-3",
   600  						"agent-state":      "down",
   601  						"agent-state-info": "(stopped: Really?)",
   602  						"series":           "quantal",
   603  						"hardware":         "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   604  					},
   605  					"4": M{
   606  						"dns-name":         "dummyenv-4.dns",
   607  						"instance-id":      "dummyenv-4",
   608  						"agent-state":      "error",
   609  						"agent-state-info": "Beware the red toys",
   610  						"series":           "quantal",
   611  						"hardware":         "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   612  					},
   613  					"5": M{
   614  						"life":        "dead",
   615  						"instance-id": "pending",
   616  						"series":      "quantal",
   617  					},
   618  				},
   619  				"services": M{
   620  					"exposed-service": M{
   621  						"charm":   "cs:quantal/dummy-1",
   622  						"exposed": true,
   623  						"units": M{
   624  							"exposed-service/0": M{
   625  								"machine":          "2",
   626  								"agent-state":      "error",
   627  								"agent-state-info": "You Require More Vespene Gas",
   628  								"open-ports": L{
   629  									"2/tcp", "3/tcp", "2/udp", "10/udp",
   630  								},
   631  								"public-address": "dummyenv-2.dns",
   632  							},
   633  						},
   634  					},
   635  					"dummy-service": M{
   636  						"charm":   "cs:quantal/dummy-1",
   637  						"exposed": false,
   638  						"units": M{
   639  							"dummy-service/0": M{
   640  								"machine":          "1",
   641  								"life":             "dying",
   642  								"agent-state":      "down",
   643  								"agent-state-info": "(started)",
   644  								"public-address":   "dummyenv-1.dns",
   645  							},
   646  						},
   647  					},
   648  				},
   649  			},
   650  		},
   651  
   652  		scopedExpect{
   653  			"scope status on dummy-service/0 unit",
   654  			[]string{"dummy-service/0"},
   655  			M{
   656  				"environment": "dummyenv",
   657  				"machines": M{
   658  					"1": machine1,
   659  				},
   660  				"services": M{
   661  					"dummy-service": M{
   662  						"charm":   "cs:quantal/dummy-1",
   663  						"exposed": false,
   664  						"units": M{
   665  							"dummy-service/0": M{
   666  								"machine":          "1",
   667  								"life":             "dying",
   668  								"agent-state":      "down",
   669  								"agent-state-info": "(started)",
   670  								"public-address":   "dummyenv-1.dns",
   671  							},
   672  						},
   673  					},
   674  				},
   675  			},
   676  		},
   677  		scopedExpect{
   678  			"scope status on exposed-service service",
   679  			[]string{"exposed-service"},
   680  			M{
   681  				"environment": "dummyenv",
   682  				"machines": M{
   683  					"2": machine2,
   684  				},
   685  				"services": M{
   686  					"exposed-service": M{
   687  						"charm":   "cs:quantal/dummy-1",
   688  						"exposed": true,
   689  						"units": M{
   690  							"exposed-service/0": M{
   691  								"machine":          "2",
   692  								"agent-state":      "error",
   693  								"agent-state-info": "You Require More Vespene Gas",
   694  								"open-ports": L{
   695  									"2/tcp", "3/tcp", "2/udp", "10/udp",
   696  								},
   697  								"public-address": "dummyenv-2.dns",
   698  							},
   699  						},
   700  					},
   701  				},
   702  			},
   703  		},
   704  		scopedExpect{
   705  			"scope status on service pattern",
   706  			[]string{"d*-service"},
   707  			M{
   708  				"environment": "dummyenv",
   709  				"machines": M{
   710  					"1": machine1,
   711  				},
   712  				"services": M{
   713  					"dummy-service": M{
   714  						"charm":   "cs:quantal/dummy-1",
   715  						"exposed": false,
   716  						"units": M{
   717  							"dummy-service/0": M{
   718  								"machine":          "1",
   719  								"life":             "dying",
   720  								"agent-state":      "down",
   721  								"agent-state-info": "(started)",
   722  								"public-address":   "dummyenv-1.dns",
   723  							},
   724  						},
   725  					},
   726  				},
   727  			},
   728  		},
   729  		scopedExpect{
   730  			"scope status on unit pattern",
   731  			[]string{"e*posed-service/*"},
   732  			M{
   733  				"environment": "dummyenv",
   734  				"machines": M{
   735  					"2": machine2,
   736  				},
   737  				"services": M{
   738  					"exposed-service": M{
   739  						"charm":   "cs:quantal/dummy-1",
   740  						"exposed": true,
   741  						"units": M{
   742  							"exposed-service/0": M{
   743  								"machine":          "2",
   744  								"agent-state":      "error",
   745  								"agent-state-info": "You Require More Vespene Gas",
   746  								"open-ports": L{
   747  									"2/tcp", "3/tcp", "2/udp", "10/udp",
   748  								},
   749  								"public-address": "dummyenv-2.dns",
   750  							},
   751  						},
   752  					},
   753  				},
   754  			},
   755  		},
   756  		scopedExpect{
   757  			"scope status on combination of service and unit patterns",
   758  			[]string{"exposed-service", "dummy-service", "e*posed-service/*", "dummy-service/*"},
   759  			M{
   760  				"environment": "dummyenv",
   761  				"machines": M{
   762  					"1": machine1,
   763  					"2": machine2,
   764  				},
   765  				"services": M{
   766  					"dummy-service": M{
   767  						"charm":   "cs:quantal/dummy-1",
   768  						"exposed": false,
   769  						"units": M{
   770  							"dummy-service/0": M{
   771  								"machine":          "1",
   772  								"life":             "dying",
   773  								"agent-state":      "down",
   774  								"agent-state-info": "(started)",
   775  								"public-address":   "dummyenv-1.dns",
   776  							},
   777  						},
   778  					},
   779  					"exposed-service": M{
   780  						"charm":   "cs:quantal/dummy-1",
   781  						"exposed": true,
   782  						"units": M{
   783  							"exposed-service/0": M{
   784  								"machine":          "2",
   785  								"agent-state":      "error",
   786  								"agent-state-info": "You Require More Vespene Gas",
   787  								"open-ports": L{
   788  									"2/tcp", "3/tcp", "2/udp", "10/udp",
   789  								},
   790  								"public-address": "dummyenv-2.dns",
   791  							},
   792  						},
   793  					},
   794  				},
   795  			},
   796  		},
   797  	), test(
   798  		"add a dying service",
   799  		addCharm{"dummy"},
   800  		addService{name: "dummy-service", charm: "dummy"},
   801  		addMachine{machineId: "0", job: state.JobHostUnits},
   802  		addUnit{"dummy-service", "0"},
   803  		ensureDyingService{"dummy-service"},
   804  		expect{
   805  			"service shows life==dying",
   806  			M{
   807  				"environment": "dummyenv",
   808  				"machines": M{
   809  					"0": M{
   810  						"instance-id": "pending",
   811  						"series":      "quantal",
   812  					},
   813  				},
   814  				"services": M{
   815  					"dummy-service": M{
   816  						"charm":   "cs:quantal/dummy-1",
   817  						"exposed": false,
   818  						"life":    "dying",
   819  						"units": M{
   820  							"dummy-service/0": M{
   821  								"machine":     "0",
   822  								"agent-state": "pending",
   823  							},
   824  						},
   825  					},
   826  				},
   827  			},
   828  		},
   829  	),
   830  
   831  	// Relation tests
   832  	test(
   833  		"complex scenario with multiple related services",
   834  		addMachine{machineId: "0", job: state.JobManageEnviron},
   835  		setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
   836  		startAliveMachine{"0"},
   837  		setMachineStatus{"0", params.StatusStarted, ""},
   838  		addCharm{"wordpress"},
   839  		addCharm{"mysql"},
   840  		addCharm{"varnish"},
   841  
   842  		addService{name: "project", charm: "wordpress"},
   843  		setServiceExposed{"project", true},
   844  		addMachine{machineId: "1", job: state.JobHostUnits},
   845  		setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
   846  		startAliveMachine{"1"},
   847  		setMachineStatus{"1", params.StatusStarted, ""},
   848  		addAliveUnit{"project", "1"},
   849  		setUnitStatus{"project/0", params.StatusStarted, ""},
   850  
   851  		addService{name: "mysql", charm: "mysql"},
   852  		setServiceExposed{"mysql", true},
   853  		addMachine{machineId: "2", job: state.JobHostUnits},
   854  		setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
   855  		startAliveMachine{"2"},
   856  		setMachineStatus{"2", params.StatusStarted, ""},
   857  		addAliveUnit{"mysql", "2"},
   858  		setUnitStatus{"mysql/0", params.StatusStarted, ""},
   859  
   860  		addService{name: "varnish", charm: "varnish"},
   861  		setServiceExposed{"varnish", true},
   862  		addMachine{machineId: "3", job: state.JobHostUnits},
   863  		setAddresses{"3", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}},
   864  		startAliveMachine{"3"},
   865  		setMachineStatus{"3", params.StatusStarted, ""},
   866  		addUnit{"varnish", "3"},
   867  
   868  		addService{name: "private", charm: "wordpress"},
   869  		setServiceExposed{"private", true},
   870  		addMachine{machineId: "4", job: state.JobHostUnits},
   871  		setAddresses{"4", []network.Address{network.NewAddress("dummyenv-4.dns", network.ScopeUnknown)}},
   872  		startAliveMachine{"4"},
   873  		setMachineStatus{"4", params.StatusStarted, ""},
   874  		addUnit{"private", "4"},
   875  
   876  		relateServices{"project", "mysql"},
   877  		relateServices{"project", "varnish"},
   878  		relateServices{"private", "mysql"},
   879  
   880  		expect{
   881  			"multiples services with relations between some of them",
   882  			M{
   883  				"environment": "dummyenv",
   884  				"machines": M{
   885  					"0": machine0,
   886  					"1": machine1,
   887  					"2": machine2,
   888  					"3": machine3,
   889  					"4": machine4,
   890  				},
   891  				"services": M{
   892  					"project": M{
   893  						"charm":   "cs:quantal/wordpress-3",
   894  						"exposed": true,
   895  						"units": M{
   896  							"project/0": M{
   897  								"machine":        "1",
   898  								"agent-state":    "started",
   899  								"public-address": "dummyenv-1.dns",
   900  							},
   901  						},
   902  						"relations": M{
   903  							"db":    L{"mysql"},
   904  							"cache": L{"varnish"},
   905  						},
   906  					},
   907  					"mysql": M{
   908  						"charm":   "cs:quantal/mysql-1",
   909  						"exposed": true,
   910  						"units": M{
   911  							"mysql/0": M{
   912  								"machine":        "2",
   913  								"agent-state":    "started",
   914  								"public-address": "dummyenv-2.dns",
   915  							},
   916  						},
   917  						"relations": M{
   918  							"server": L{"private", "project"},
   919  						},
   920  					},
   921  					"varnish": M{
   922  						"charm":   "cs:quantal/varnish-1",
   923  						"exposed": true,
   924  						"units": M{
   925  							"varnish/0": M{
   926  								"machine":        "3",
   927  								"agent-state":    "pending",
   928  								"public-address": "dummyenv-3.dns",
   929  							},
   930  						},
   931  						"relations": M{
   932  							"webcache": L{"project"},
   933  						},
   934  					},
   935  					"private": M{
   936  						"charm":   "cs:quantal/wordpress-3",
   937  						"exposed": true,
   938  						"units": M{
   939  							"private/0": M{
   940  								"machine":        "4",
   941  								"agent-state":    "pending",
   942  								"public-address": "dummyenv-4.dns",
   943  							},
   944  						},
   945  						"relations": M{
   946  							"db": L{"mysql"},
   947  						},
   948  					},
   949  				},
   950  			},
   951  		},
   952  	), test(
   953  		"simple peer scenario",
   954  		addMachine{machineId: "0", job: state.JobManageEnviron},
   955  		setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
   956  		startAliveMachine{"0"},
   957  		setMachineStatus{"0", params.StatusStarted, ""},
   958  		addCharm{"riak"},
   959  		addCharm{"wordpress"},
   960  
   961  		addService{name: "riak", charm: "riak"},
   962  		setServiceExposed{"riak", true},
   963  		addMachine{machineId: "1", job: state.JobHostUnits},
   964  		setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
   965  		startAliveMachine{"1"},
   966  		setMachineStatus{"1", params.StatusStarted, ""},
   967  		addAliveUnit{"riak", "1"},
   968  		setUnitStatus{"riak/0", params.StatusStarted, ""},
   969  		addMachine{machineId: "2", job: state.JobHostUnits},
   970  		setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
   971  		startAliveMachine{"2"},
   972  		setMachineStatus{"2", params.StatusStarted, ""},
   973  		addAliveUnit{"riak", "2"},
   974  		setUnitStatus{"riak/1", params.StatusStarted, ""},
   975  		addMachine{machineId: "3", job: state.JobHostUnits},
   976  		setAddresses{"3", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}},
   977  		startAliveMachine{"3"},
   978  		setMachineStatus{"3", params.StatusStarted, ""},
   979  		addAliveUnit{"riak", "3"},
   980  		setUnitStatus{"riak/2", params.StatusStarted, ""},
   981  
   982  		expect{
   983  			"multiples related peer units",
   984  			M{
   985  				"environment": "dummyenv",
   986  				"machines": M{
   987  					"0": machine0,
   988  					"1": machine1,
   989  					"2": machine2,
   990  					"3": machine3,
   991  				},
   992  				"services": M{
   993  					"riak": M{
   994  						"charm":   "cs:quantal/riak-7",
   995  						"exposed": true,
   996  						"units": M{
   997  							"riak/0": M{
   998  								"machine":        "1",
   999  								"agent-state":    "started",
  1000  								"public-address": "dummyenv-1.dns",
  1001  							},
  1002  							"riak/1": M{
  1003  								"machine":        "2",
  1004  								"agent-state":    "started",
  1005  								"public-address": "dummyenv-2.dns",
  1006  							},
  1007  							"riak/2": M{
  1008  								"machine":        "3",
  1009  								"agent-state":    "started",
  1010  								"public-address": "dummyenv-3.dns",
  1011  							},
  1012  						},
  1013  						"relations": M{
  1014  							"ring": L{"riak"},
  1015  						},
  1016  					},
  1017  				},
  1018  			},
  1019  		},
  1020  	),
  1021  
  1022  	// Subordinate tests
  1023  	test(
  1024  		"one service with one subordinate service",
  1025  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1026  		setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1027  		startAliveMachine{"0"},
  1028  		setMachineStatus{"0", params.StatusStarted, ""},
  1029  		addCharm{"wordpress"},
  1030  		addCharm{"mysql"},
  1031  		addCharm{"logging"},
  1032  
  1033  		addService{name: "wordpress", charm: "wordpress"},
  1034  		setServiceExposed{"wordpress", true},
  1035  		addMachine{machineId: "1", job: state.JobHostUnits},
  1036  		setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1037  		startAliveMachine{"1"},
  1038  		setMachineStatus{"1", params.StatusStarted, ""},
  1039  		addAliveUnit{"wordpress", "1"},
  1040  		setUnitStatus{"wordpress/0", params.StatusStarted, ""},
  1041  
  1042  		addService{name: "mysql", charm: "mysql"},
  1043  		setServiceExposed{"mysql", true},
  1044  		addMachine{machineId: "2", job: state.JobHostUnits},
  1045  		setAddresses{"2", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
  1046  		startAliveMachine{"2"},
  1047  		setMachineStatus{"2", params.StatusStarted, ""},
  1048  		addAliveUnit{"mysql", "2"},
  1049  		setUnitStatus{"mysql/0", params.StatusStarted, ""},
  1050  
  1051  		addService{name: "logging", charm: "logging"},
  1052  		setServiceExposed{"logging", true},
  1053  
  1054  		relateServices{"wordpress", "mysql"},
  1055  		relateServices{"wordpress", "logging"},
  1056  		relateServices{"mysql", "logging"},
  1057  
  1058  		addSubordinate{"wordpress/0", "logging"},
  1059  		addSubordinate{"mysql/0", "logging"},
  1060  
  1061  		setUnitsAlive{"logging"},
  1062  		setUnitStatus{"logging/0", params.StatusStarted, ""},
  1063  		setUnitStatus{"logging/1", params.StatusError, "somehow lost in all those logs"},
  1064  
  1065  		expect{
  1066  			"multiples related peer units",
  1067  			M{
  1068  				"environment": "dummyenv",
  1069  				"machines": M{
  1070  					"0": machine0,
  1071  					"1": machine1,
  1072  					"2": machine2,
  1073  				},
  1074  				"services": M{
  1075  					"wordpress": M{
  1076  						"charm":   "cs:quantal/wordpress-3",
  1077  						"exposed": true,
  1078  						"units": M{
  1079  							"wordpress/0": M{
  1080  								"machine":     "1",
  1081  								"agent-state": "started",
  1082  								"subordinates": M{
  1083  									"logging/0": M{
  1084  										"agent-state":    "started",
  1085  										"public-address": "dummyenv-1.dns",
  1086  									},
  1087  								},
  1088  								"public-address": "dummyenv-1.dns",
  1089  							},
  1090  						},
  1091  						"relations": M{
  1092  							"db":          L{"mysql"},
  1093  							"logging-dir": L{"logging"},
  1094  						},
  1095  					},
  1096  					"mysql": M{
  1097  						"charm":   "cs:quantal/mysql-1",
  1098  						"exposed": true,
  1099  						"units": M{
  1100  							"mysql/0": M{
  1101  								"machine":     "2",
  1102  								"agent-state": "started",
  1103  								"subordinates": M{
  1104  									"logging/1": M{
  1105  										"agent-state":      "error",
  1106  										"agent-state-info": "somehow lost in all those logs",
  1107  										"public-address":   "dummyenv-2.dns",
  1108  									},
  1109  								},
  1110  								"public-address": "dummyenv-2.dns",
  1111  							},
  1112  						},
  1113  						"relations": M{
  1114  							"server":    L{"wordpress"},
  1115  							"juju-info": L{"logging"},
  1116  						},
  1117  					},
  1118  					"logging": M{
  1119  						"charm":   "cs:quantal/logging-1",
  1120  						"exposed": true,
  1121  						"relations": M{
  1122  							"logging-directory": L{"wordpress"},
  1123  							"info":              L{"mysql"},
  1124  						},
  1125  						"subordinate-to": L{"mysql", "wordpress"},
  1126  					},
  1127  				},
  1128  			},
  1129  		},
  1130  
  1131  		// scoped on 'logging'
  1132  		scopedExpect{
  1133  			"subordinates scoped on logging",
  1134  			[]string{"logging"},
  1135  			M{
  1136  				"environment": "dummyenv",
  1137  				"machines": M{
  1138  					"1": machine1,
  1139  					"2": machine2,
  1140  				},
  1141  				"services": M{
  1142  					"wordpress": M{
  1143  						"charm":   "cs:quantal/wordpress-3",
  1144  						"exposed": true,
  1145  						"units": M{
  1146  							"wordpress/0": M{
  1147  								"machine":     "1",
  1148  								"agent-state": "started",
  1149  								"subordinates": M{
  1150  									"logging/0": M{
  1151  										"agent-state":    "started",
  1152  										"public-address": "dummyenv-1.dns",
  1153  									},
  1154  								},
  1155  								"public-address": "dummyenv-1.dns",
  1156  							},
  1157  						},
  1158  						"relations": M{
  1159  							"db":          L{"mysql"},
  1160  							"logging-dir": L{"logging"},
  1161  						},
  1162  					},
  1163  					"mysql": M{
  1164  						"charm":   "cs:quantal/mysql-1",
  1165  						"exposed": true,
  1166  						"units": M{
  1167  							"mysql/0": M{
  1168  								"machine":     "2",
  1169  								"agent-state": "started",
  1170  								"subordinates": M{
  1171  									"logging/1": M{
  1172  										"agent-state":      "error",
  1173  										"agent-state-info": "somehow lost in all those logs",
  1174  										"public-address":   "dummyenv-2.dns",
  1175  									},
  1176  								},
  1177  								"public-address": "dummyenv-2.dns",
  1178  							},
  1179  						},
  1180  						"relations": M{
  1181  							"server":    L{"wordpress"},
  1182  							"juju-info": L{"logging"},
  1183  						},
  1184  					},
  1185  					"logging": M{
  1186  						"charm":   "cs:quantal/logging-1",
  1187  						"exposed": true,
  1188  						"relations": M{
  1189  							"logging-directory": L{"wordpress"},
  1190  							"info":              L{"mysql"},
  1191  						},
  1192  						"subordinate-to": L{"mysql", "wordpress"},
  1193  					},
  1194  				},
  1195  			},
  1196  		},
  1197  
  1198  		// scoped on wordpress/0
  1199  		scopedExpect{
  1200  			"subordinates scoped on logging",
  1201  			[]string{"wordpress/0"},
  1202  			M{
  1203  				"environment": "dummyenv",
  1204  				"machines": M{
  1205  					"1": machine1,
  1206  				},
  1207  				"services": M{
  1208  					"wordpress": M{
  1209  						"charm":   "cs:quantal/wordpress-3",
  1210  						"exposed": true,
  1211  						"units": M{
  1212  							"wordpress/0": M{
  1213  								"machine":     "1",
  1214  								"agent-state": "started",
  1215  								"subordinates": M{
  1216  									"logging/0": M{
  1217  										"agent-state":    "started",
  1218  										"public-address": "dummyenv-1.dns",
  1219  									},
  1220  								},
  1221  								"public-address": "dummyenv-1.dns",
  1222  							},
  1223  						},
  1224  						"relations": M{
  1225  							"db":          L{"mysql"},
  1226  							"logging-dir": L{"logging"},
  1227  						},
  1228  					},
  1229  					"logging": M{
  1230  						"charm":   "cs:quantal/logging-1",
  1231  						"exposed": true,
  1232  						"relations": M{
  1233  							"logging-directory": L{"wordpress"},
  1234  							"info":              L{"mysql"},
  1235  						},
  1236  						"subordinate-to": L{"mysql", "wordpress"},
  1237  					},
  1238  				},
  1239  			},
  1240  		},
  1241  	), test(
  1242  		"one service with two subordinate services",
  1243  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1244  		startAliveMachine{"0"},
  1245  		setMachineStatus{"0", params.StatusStarted, ""},
  1246  		addCharm{"wordpress"},
  1247  		addCharm{"logging"},
  1248  		addCharm{"monitoring"},
  1249  
  1250  		addService{name: "wordpress", charm: "wordpress"},
  1251  		setServiceExposed{"wordpress", true},
  1252  		addMachine{machineId: "1", job: state.JobHostUnits},
  1253  		setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1254  		startAliveMachine{"1"},
  1255  		setMachineStatus{"1", params.StatusStarted, ""},
  1256  		addAliveUnit{"wordpress", "1"},
  1257  		setUnitStatus{"wordpress/0", params.StatusStarted, ""},
  1258  
  1259  		addService{name: "logging", charm: "logging"},
  1260  		setServiceExposed{"logging", true},
  1261  		addService{name: "monitoring", charm: "monitoring"},
  1262  		setServiceExposed{"monitoring", true},
  1263  
  1264  		relateServices{"wordpress", "logging"},
  1265  		relateServices{"wordpress", "monitoring"},
  1266  
  1267  		addSubordinate{"wordpress/0", "logging"},
  1268  		addSubordinate{"wordpress/0", "monitoring"},
  1269  
  1270  		setUnitsAlive{"logging"},
  1271  		setUnitStatus{"logging/0", params.StatusStarted, ""},
  1272  
  1273  		setUnitsAlive{"monitoring"},
  1274  		setUnitStatus{"monitoring/0", params.StatusStarted, ""},
  1275  
  1276  		// scoped on monitoring; make sure logging doesn't show up.
  1277  		scopedExpect{
  1278  			"subordinates scoped on:",
  1279  			[]string{"monitoring"},
  1280  			M{
  1281  				"environment": "dummyenv",
  1282  				"machines": M{
  1283  					"1": machine1,
  1284  				},
  1285  				"services": M{
  1286  					"wordpress": M{
  1287  						"charm":   "cs:quantal/wordpress-3",
  1288  						"exposed": true,
  1289  						"units": M{
  1290  							"wordpress/0": M{
  1291  								"machine":     "1",
  1292  								"agent-state": "started",
  1293  								"subordinates": M{
  1294  									"monitoring/0": M{
  1295  										"agent-state":    "started",
  1296  										"public-address": "dummyenv-1.dns",
  1297  									},
  1298  								},
  1299  								"public-address": "dummyenv-1.dns",
  1300  							},
  1301  						},
  1302  						"relations": M{
  1303  							"logging-dir":     L{"logging"},
  1304  							"monitoring-port": L{"monitoring"},
  1305  						},
  1306  					},
  1307  					"monitoring": M{
  1308  						"charm":   "cs:quantal/monitoring-0",
  1309  						"exposed": true,
  1310  						"relations": M{
  1311  							"monitoring-port": L{"wordpress"},
  1312  						},
  1313  						"subordinate-to": L{"wordpress"},
  1314  					},
  1315  				},
  1316  			},
  1317  		},
  1318  	), test(
  1319  		"machines with containers",
  1320  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1321  		setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1322  		startAliveMachine{"0"},
  1323  		setMachineStatus{"0", params.StatusStarted, ""},
  1324  		addCharm{"mysql"},
  1325  		addService{name: "mysql", charm: "mysql"},
  1326  		setServiceExposed{"mysql", true},
  1327  
  1328  		addMachine{machineId: "1", job: state.JobHostUnits},
  1329  		setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1330  		startAliveMachine{"1"},
  1331  		setMachineStatus{"1", params.StatusStarted, ""},
  1332  		addAliveUnit{"mysql", "1"},
  1333  		setUnitStatus{"mysql/0", params.StatusStarted, ""},
  1334  
  1335  		// A container on machine 1.
  1336  		addContainer{"1", "1/lxc/0", state.JobHostUnits},
  1337  		setAddresses{"1/lxc/0", []network.Address{network.NewAddress("dummyenv-2.dns", network.ScopeUnknown)}},
  1338  		startAliveMachine{"1/lxc/0"},
  1339  		setMachineStatus{"1/lxc/0", params.StatusStarted, ""},
  1340  		addAliveUnit{"mysql", "1/lxc/0"},
  1341  		setUnitStatus{"mysql/1", params.StatusStarted, ""},
  1342  		addContainer{"1", "1/lxc/1", state.JobHostUnits},
  1343  
  1344  		// A nested container.
  1345  		addContainer{"1/lxc/0", "1/lxc/0/lxc/0", state.JobHostUnits},
  1346  		setAddresses{"1/lxc/0/lxc/0", []network.Address{network.NewAddress("dummyenv-3.dns", network.ScopeUnknown)}},
  1347  		startAliveMachine{"1/lxc/0/lxc/0"},
  1348  		setMachineStatus{"1/lxc/0/lxc/0", params.StatusStarted, ""},
  1349  
  1350  		expect{
  1351  			"machines with nested containers",
  1352  			M{
  1353  				"environment": "dummyenv",
  1354  				"machines": M{
  1355  					"0": machine0,
  1356  					"1": machine1WithContainers,
  1357  				},
  1358  				"services": M{
  1359  					"mysql": M{
  1360  						"charm":   "cs:quantal/mysql-1",
  1361  						"exposed": true,
  1362  						"units": M{
  1363  							"mysql/0": M{
  1364  								"machine":        "1",
  1365  								"agent-state":    "started",
  1366  								"public-address": "dummyenv-1.dns",
  1367  							},
  1368  							"mysql/1": M{
  1369  								"machine":        "1/lxc/0",
  1370  								"agent-state":    "started",
  1371  								"public-address": "dummyenv-2.dns",
  1372  							},
  1373  						},
  1374  					},
  1375  				},
  1376  			},
  1377  		},
  1378  
  1379  		// once again, with a scope on mysql/1
  1380  		scopedExpect{
  1381  			"machines with nested containers",
  1382  			[]string{"mysql/1"},
  1383  			M{
  1384  				"environment": "dummyenv",
  1385  				"machines": M{
  1386  					"1": machine1WithContainersScoped,
  1387  				},
  1388  				"services": M{
  1389  					"mysql": M{
  1390  						"charm":   "cs:quantal/mysql-1",
  1391  						"exposed": true,
  1392  						"units": M{
  1393  							"mysql/1": M{
  1394  								"machine":        "1/lxc/0",
  1395  								"agent-state":    "started",
  1396  								"public-address": "dummyenv-2.dns",
  1397  							},
  1398  						},
  1399  					},
  1400  				},
  1401  			},
  1402  		},
  1403  	), test(
  1404  		"service with out of date charm",
  1405  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1406  		setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1407  		startAliveMachine{"0"},
  1408  		setMachineStatus{"0", params.StatusStarted, ""},
  1409  		addMachine{machineId: "1", job: state.JobHostUnits},
  1410  		setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1411  		startAliveMachine{"1"},
  1412  		setMachineStatus{"1", params.StatusStarted, ""},
  1413  		addCharm{"mysql"},
  1414  		addService{name: "mysql", charm: "mysql"},
  1415  		setServiceExposed{"mysql", true},
  1416  		addCharmPlaceholder{"mysql", 23},
  1417  		addAliveUnit{"mysql", "1"},
  1418  
  1419  		expect{
  1420  			"services and units with correct charm status",
  1421  			M{
  1422  				"environment": "dummyenv",
  1423  				"machines": M{
  1424  					"0": machine0,
  1425  					"1": machine1,
  1426  				},
  1427  				"services": M{
  1428  					"mysql": M{
  1429  						"charm":          "cs:quantal/mysql-1",
  1430  						"can-upgrade-to": "cs:quantal/mysql-23",
  1431  						"exposed":        true,
  1432  						"units": M{
  1433  							"mysql/0": M{
  1434  								"machine":        "1",
  1435  								"agent-state":    "pending",
  1436  								"public-address": "dummyenv-1.dns",
  1437  							},
  1438  						},
  1439  					},
  1440  				},
  1441  			},
  1442  		},
  1443  	), test(
  1444  		"unit with out of date charm",
  1445  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1446  		setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1447  		startAliveMachine{"0"},
  1448  		setMachineStatus{"0", params.StatusStarted, ""},
  1449  		addMachine{machineId: "1", job: state.JobHostUnits},
  1450  		setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1451  		startAliveMachine{"1"},
  1452  		setMachineStatus{"1", params.StatusStarted, ""},
  1453  		addCharm{"mysql"},
  1454  		addService{name: "mysql", charm: "mysql"},
  1455  		setServiceExposed{"mysql", true},
  1456  		addAliveUnit{"mysql", "1"},
  1457  		setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  1458  		addCharmWithRevision{addCharm{"mysql"}, "local", 1},
  1459  		setServiceCharm{"mysql", "local:quantal/mysql-1"},
  1460  
  1461  		expect{
  1462  			"services and units with correct charm status",
  1463  			M{
  1464  				"environment": "dummyenv",
  1465  				"machines": M{
  1466  					"0": machine0,
  1467  					"1": machine1,
  1468  				},
  1469  				"services": M{
  1470  					"mysql": M{
  1471  						"charm":   "local:quantal/mysql-1",
  1472  						"exposed": true,
  1473  						"units": M{
  1474  							"mysql/0": M{
  1475  								"machine":        "1",
  1476  								"agent-state":    "started",
  1477  								"upgrading-from": "cs:quantal/mysql-1",
  1478  								"public-address": "dummyenv-1.dns",
  1479  							},
  1480  						},
  1481  					},
  1482  				},
  1483  			},
  1484  		},
  1485  	), test(
  1486  		"service and unit with out of date charms",
  1487  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1488  		setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1489  		startAliveMachine{"0"},
  1490  		setMachineStatus{"0", params.StatusStarted, ""},
  1491  		addMachine{machineId: "1", job: state.JobHostUnits},
  1492  		setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1493  		startAliveMachine{"1"},
  1494  		setMachineStatus{"1", params.StatusStarted, ""},
  1495  		addCharm{"mysql"},
  1496  		addService{name: "mysql", charm: "mysql"},
  1497  		setServiceExposed{"mysql", true},
  1498  		addAliveUnit{"mysql", "1"},
  1499  		setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  1500  		addCharmWithRevision{addCharm{"mysql"}, "cs", 2},
  1501  		setServiceCharm{"mysql", "cs:quantal/mysql-2"},
  1502  		addCharmPlaceholder{"mysql", 23},
  1503  
  1504  		expect{
  1505  			"services and units with correct charm status",
  1506  			M{
  1507  				"environment": "dummyenv",
  1508  				"machines": M{
  1509  					"0": machine0,
  1510  					"1": machine1,
  1511  				},
  1512  				"services": M{
  1513  					"mysql": M{
  1514  						"charm":          "cs:quantal/mysql-2",
  1515  						"can-upgrade-to": "cs:quantal/mysql-23",
  1516  						"exposed":        true,
  1517  						"units": M{
  1518  							"mysql/0": M{
  1519  								"machine":        "1",
  1520  								"agent-state":    "started",
  1521  								"upgrading-from": "cs:quantal/mysql-1",
  1522  								"public-address": "dummyenv-1.dns",
  1523  							},
  1524  						},
  1525  					},
  1526  				},
  1527  			},
  1528  		},
  1529  	), test(
  1530  		"service with local charm not shown as out of date",
  1531  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1532  		setAddresses{"0", []network.Address{network.NewAddress("dummyenv-0.dns", network.ScopeUnknown)}},
  1533  		startAliveMachine{"0"},
  1534  		setMachineStatus{"0", params.StatusStarted, ""},
  1535  		addMachine{machineId: "1", job: state.JobHostUnits},
  1536  		setAddresses{"1", []network.Address{network.NewAddress("dummyenv-1.dns", network.ScopeUnknown)}},
  1537  		startAliveMachine{"1"},
  1538  		setMachineStatus{"1", params.StatusStarted, ""},
  1539  		addCharm{"mysql"},
  1540  		addService{name: "mysql", charm: "mysql"},
  1541  		setServiceExposed{"mysql", true},
  1542  		addAliveUnit{"mysql", "1"},
  1543  		setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  1544  		addCharmWithRevision{addCharm{"mysql"}, "local", 1},
  1545  		setServiceCharm{"mysql", "local:quantal/mysql-1"},
  1546  		addCharmPlaceholder{"mysql", 23},
  1547  
  1548  		expect{
  1549  			"services and units with correct charm status",
  1550  			M{
  1551  				"environment": "dummyenv",
  1552  				"machines": M{
  1553  					"0": machine0,
  1554  					"1": machine1,
  1555  				},
  1556  				"services": M{
  1557  					"mysql": M{
  1558  						"charm":   "local:quantal/mysql-1",
  1559  						"exposed": true,
  1560  						"units": M{
  1561  							"mysql/0": M{
  1562  								"machine":        "1",
  1563  								"agent-state":    "started",
  1564  								"upgrading-from": "cs:quantal/mysql-1",
  1565  								"public-address": "dummyenv-1.dns",
  1566  							},
  1567  						},
  1568  					},
  1569  				},
  1570  			},
  1571  		},
  1572  	),
  1573  }
  1574  
  1575  // TODO(dfc) test failing components by destructively mutating the state under the hood
  1576  
  1577  type addMachine struct {
  1578  	machineId string
  1579  	cons      constraints.Value
  1580  	job       state.MachineJob
  1581  }
  1582  
  1583  func (am addMachine) step(c *gc.C, ctx *context) {
  1584  	m, err := ctx.st.AddOneMachine(state.MachineTemplate{
  1585  		Series:      "quantal",
  1586  		Constraints: am.cons,
  1587  		Jobs:        []state.MachineJob{am.job},
  1588  	})
  1589  	c.Assert(err, gc.IsNil)
  1590  	c.Assert(m.Id(), gc.Equals, am.machineId)
  1591  }
  1592  
  1593  type addNetwork struct {
  1594  	name       string
  1595  	providerId network.Id
  1596  	cidr       string
  1597  	vlanTag    int
  1598  }
  1599  
  1600  func (an addNetwork) step(c *gc.C, ctx *context) {
  1601  	n, err := ctx.st.AddNetwork(state.NetworkInfo{
  1602  		Name:       an.name,
  1603  		ProviderId: an.providerId,
  1604  		CIDR:       an.cidr,
  1605  		VLANTag:    an.vlanTag,
  1606  	})
  1607  	c.Assert(err, gc.IsNil)
  1608  	c.Assert(n.Name(), gc.Equals, an.name)
  1609  }
  1610  
  1611  type addContainer struct {
  1612  	parentId  string
  1613  	machineId string
  1614  	job       state.MachineJob
  1615  }
  1616  
  1617  func (ac addContainer) step(c *gc.C, ctx *context) {
  1618  	template := state.MachineTemplate{
  1619  		Series: "quantal",
  1620  		Jobs:   []state.MachineJob{ac.job},
  1621  	}
  1622  	m, err := ctx.st.AddMachineInsideMachine(template, ac.parentId, instance.LXC)
  1623  	c.Assert(err, gc.IsNil)
  1624  	c.Assert(m.Id(), gc.Equals, ac.machineId)
  1625  }
  1626  
  1627  type startMachine struct {
  1628  	machineId string
  1629  }
  1630  
  1631  func (sm startMachine) step(c *gc.C, ctx *context) {
  1632  	m, err := ctx.st.Machine(sm.machineId)
  1633  	c.Assert(err, gc.IsNil)
  1634  	cons, err := m.Constraints()
  1635  	c.Assert(err, gc.IsNil)
  1636  	inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.conn.Environ, m.Id(), cons)
  1637  	err = m.SetProvisioned(inst.Id(), "fake_nonce", hc)
  1638  	c.Assert(err, gc.IsNil)
  1639  }
  1640  
  1641  type startMissingMachine struct {
  1642  	machineId string
  1643  }
  1644  
  1645  func (sm startMissingMachine) step(c *gc.C, ctx *context) {
  1646  	m, err := ctx.st.Machine(sm.machineId)
  1647  	c.Assert(err, gc.IsNil)
  1648  	cons, err := m.Constraints()
  1649  	c.Assert(err, gc.IsNil)
  1650  	_, hc := testing.AssertStartInstanceWithConstraints(c, ctx.conn.Environ, m.Id(), cons)
  1651  	err = m.SetProvisioned("i-missing", "fake_nonce", hc)
  1652  	c.Assert(err, gc.IsNil)
  1653  	err = m.SetInstanceStatus("missing")
  1654  	c.Assert(err, gc.IsNil)
  1655  }
  1656  
  1657  type startAliveMachine struct {
  1658  	machineId string
  1659  }
  1660  
  1661  func (sam startAliveMachine) step(c *gc.C, ctx *context) {
  1662  	m, err := ctx.st.Machine(sam.machineId)
  1663  	c.Assert(err, gc.IsNil)
  1664  	pinger := ctx.setAgentAlive(c, m)
  1665  	cons, err := m.Constraints()
  1666  	c.Assert(err, gc.IsNil)
  1667  	inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.conn.Environ, m.Id(), cons)
  1668  	err = m.SetProvisioned(inst.Id(), "fake_nonce", hc)
  1669  	c.Assert(err, gc.IsNil)
  1670  	ctx.pingers[m.Id()] = pinger
  1671  }
  1672  
  1673  type setAddresses struct {
  1674  	machineId string
  1675  	addresses []network.Address
  1676  }
  1677  
  1678  func (sa setAddresses) step(c *gc.C, ctx *context) {
  1679  	m, err := ctx.st.Machine(sa.machineId)
  1680  	c.Assert(err, gc.IsNil)
  1681  	err = m.SetAddresses(sa.addresses...)
  1682  	c.Assert(err, gc.IsNil)
  1683  }
  1684  
  1685  type setTools struct {
  1686  	machineId string
  1687  	version   version.Binary
  1688  }
  1689  
  1690  func (st setTools) step(c *gc.C, ctx *context) {
  1691  	m, err := ctx.st.Machine(st.machineId)
  1692  	c.Assert(err, gc.IsNil)
  1693  	err = m.SetAgentVersion(st.version)
  1694  	c.Assert(err, gc.IsNil)
  1695  }
  1696  
  1697  type addCharm struct {
  1698  	name string
  1699  }
  1700  
  1701  func (ac addCharm) addCharmStep(c *gc.C, ctx *context, scheme string, rev int) {
  1702  	ch := charmtesting.Charms.Dir(ac.name)
  1703  	name := ch.Meta().Name
  1704  	curl := charm.MustParseURL(fmt.Sprintf("%s:quantal/%s-%d", scheme, name, rev))
  1705  	bundleURL, err := url.Parse(fmt.Sprintf("http://bundles.testing.invalid/%s-%d", name, rev))
  1706  	c.Assert(err, gc.IsNil)
  1707  	dummy, err := ctx.st.AddCharm(ch, curl, bundleURL, fmt.Sprintf("%s-%d-sha256", name, rev))
  1708  	c.Assert(err, gc.IsNil)
  1709  	ctx.charms[ac.name] = dummy
  1710  }
  1711  
  1712  func (ac addCharm) step(c *gc.C, ctx *context) {
  1713  	ch := charmtesting.Charms.Dir(ac.name)
  1714  	ac.addCharmStep(c, ctx, "cs", ch.Revision())
  1715  }
  1716  
  1717  type addCharmWithRevision struct {
  1718  	addCharm
  1719  	scheme string
  1720  	rev    int
  1721  }
  1722  
  1723  func (ac addCharmWithRevision) step(c *gc.C, ctx *context) {
  1724  	ac.addCharmStep(c, ctx, ac.scheme, ac.rev)
  1725  }
  1726  
  1727  type addService struct {
  1728  	name     string
  1729  	charm    string
  1730  	networks []string
  1731  	cons     constraints.Value
  1732  }
  1733  
  1734  func (as addService) step(c *gc.C, ctx *context) {
  1735  	ch, ok := ctx.charms[as.charm]
  1736  	c.Assert(ok, gc.Equals, true)
  1737  	svc, err := ctx.st.AddService(as.name, "user-admin", ch, as.networks)
  1738  	c.Assert(err, gc.IsNil)
  1739  	if svc.IsPrincipal() {
  1740  		err = svc.SetConstraints(as.cons)
  1741  		c.Assert(err, gc.IsNil)
  1742  	}
  1743  }
  1744  
  1745  type setServiceExposed struct {
  1746  	name    string
  1747  	exposed bool
  1748  }
  1749  
  1750  func (sse setServiceExposed) step(c *gc.C, ctx *context) {
  1751  	s, err := ctx.st.Service(sse.name)
  1752  	c.Assert(err, gc.IsNil)
  1753  	if sse.exposed {
  1754  		err = s.SetExposed()
  1755  		c.Assert(err, gc.IsNil)
  1756  	}
  1757  }
  1758  
  1759  type setServiceCharm struct {
  1760  	name  string
  1761  	charm string
  1762  }
  1763  
  1764  func (ssc setServiceCharm) step(c *gc.C, ctx *context) {
  1765  	ch, err := ctx.st.Charm(charm.MustParseURL(ssc.charm))
  1766  	c.Assert(err, gc.IsNil)
  1767  	s, err := ctx.st.Service(ssc.name)
  1768  	c.Assert(err, gc.IsNil)
  1769  	err = s.SetCharm(ch, false)
  1770  	c.Assert(err, gc.IsNil)
  1771  }
  1772  
  1773  type addCharmPlaceholder struct {
  1774  	name string
  1775  	rev  int
  1776  }
  1777  
  1778  func (ac addCharmPlaceholder) step(c *gc.C, ctx *context) {
  1779  	ch := charmtesting.Charms.Dir(ac.name)
  1780  	name := ch.Meta().Name
  1781  	curl := charm.MustParseURL(fmt.Sprintf("cs:quantal/%s-%d", name, ac.rev))
  1782  	err := ctx.st.AddStoreCharmPlaceholder(curl)
  1783  	c.Assert(err, gc.IsNil)
  1784  }
  1785  
  1786  type addUnit struct {
  1787  	serviceName string
  1788  	machineId   string
  1789  }
  1790  
  1791  func (au addUnit) step(c *gc.C, ctx *context) {
  1792  	s, err := ctx.st.Service(au.serviceName)
  1793  	c.Assert(err, gc.IsNil)
  1794  	u, err := s.AddUnit()
  1795  	c.Assert(err, gc.IsNil)
  1796  	m, err := ctx.st.Machine(au.machineId)
  1797  	c.Assert(err, gc.IsNil)
  1798  	err = u.AssignToMachine(m)
  1799  	c.Assert(err, gc.IsNil)
  1800  }
  1801  
  1802  type addAliveUnit struct {
  1803  	serviceName string
  1804  	machineId   string
  1805  }
  1806  
  1807  func (aau addAliveUnit) step(c *gc.C, ctx *context) {
  1808  	s, err := ctx.st.Service(aau.serviceName)
  1809  	c.Assert(err, gc.IsNil)
  1810  	u, err := s.AddUnit()
  1811  	c.Assert(err, gc.IsNil)
  1812  	pinger := ctx.setAgentAlive(c, u)
  1813  	m, err := ctx.st.Machine(aau.machineId)
  1814  	c.Assert(err, gc.IsNil)
  1815  	err = u.AssignToMachine(m)
  1816  	c.Assert(err, gc.IsNil)
  1817  	ctx.pingers[u.Name()] = pinger
  1818  }
  1819  
  1820  type setUnitsAlive struct {
  1821  	serviceName string
  1822  }
  1823  
  1824  func (sua setUnitsAlive) step(c *gc.C, ctx *context) {
  1825  	s, err := ctx.st.Service(sua.serviceName)
  1826  	c.Assert(err, gc.IsNil)
  1827  	us, err := s.AllUnits()
  1828  	c.Assert(err, gc.IsNil)
  1829  	for _, u := range us {
  1830  		ctx.pingers[u.Name()] = ctx.setAgentAlive(c, u)
  1831  	}
  1832  }
  1833  
  1834  type setUnitStatus struct {
  1835  	unitName   string
  1836  	status     params.Status
  1837  	statusInfo string
  1838  }
  1839  
  1840  func (sus setUnitStatus) step(c *gc.C, ctx *context) {
  1841  	u, err := ctx.st.Unit(sus.unitName)
  1842  	c.Assert(err, gc.IsNil)
  1843  	err = u.SetStatus(sus.status, sus.statusInfo, nil)
  1844  	c.Assert(err, gc.IsNil)
  1845  }
  1846  
  1847  type setUnitCharmURL struct {
  1848  	unitName string
  1849  	charm    string
  1850  }
  1851  
  1852  func (uc setUnitCharmURL) step(c *gc.C, ctx *context) {
  1853  	u, err := ctx.st.Unit(uc.unitName)
  1854  	c.Assert(err, gc.IsNil)
  1855  	curl := charm.MustParseURL(uc.charm)
  1856  	err = u.SetCharmURL(curl)
  1857  	c.Assert(err, gc.IsNil)
  1858  	err = u.SetStatus(params.StatusStarted, "", nil)
  1859  	c.Assert(err, gc.IsNil)
  1860  }
  1861  
  1862  type openUnitPort struct {
  1863  	unitName string
  1864  	protocol string
  1865  	number   int
  1866  }
  1867  
  1868  func (oup openUnitPort) step(c *gc.C, ctx *context) {
  1869  	u, err := ctx.st.Unit(oup.unitName)
  1870  	c.Assert(err, gc.IsNil)
  1871  	err = u.OpenPort(oup.protocol, oup.number)
  1872  	c.Assert(err, gc.IsNil)
  1873  }
  1874  
  1875  type ensureDyingUnit struct {
  1876  	unitName string
  1877  }
  1878  
  1879  func (e ensureDyingUnit) step(c *gc.C, ctx *context) {
  1880  	u, err := ctx.st.Unit(e.unitName)
  1881  	c.Assert(err, gc.IsNil)
  1882  	err = u.Destroy()
  1883  	c.Assert(err, gc.IsNil)
  1884  	c.Assert(u.Life(), gc.Equals, state.Dying)
  1885  }
  1886  
  1887  type ensureDyingService struct {
  1888  	serviceName string
  1889  }
  1890  
  1891  func (e ensureDyingService) step(c *gc.C, ctx *context) {
  1892  	svc, err := ctx.st.Service(e.serviceName)
  1893  	c.Assert(err, gc.IsNil)
  1894  	err = svc.Destroy()
  1895  	c.Assert(err, gc.IsNil)
  1896  	err = svc.Refresh()
  1897  	c.Assert(err, gc.IsNil)
  1898  	c.Assert(svc.Life(), gc.Equals, state.Dying)
  1899  }
  1900  
  1901  type ensureDeadMachine struct {
  1902  	machineId string
  1903  }
  1904  
  1905  func (e ensureDeadMachine) step(c *gc.C, ctx *context) {
  1906  	m, err := ctx.st.Machine(e.machineId)
  1907  	c.Assert(err, gc.IsNil)
  1908  	err = m.EnsureDead()
  1909  	c.Assert(err, gc.IsNil)
  1910  	c.Assert(m.Life(), gc.Equals, state.Dead)
  1911  }
  1912  
  1913  type setMachineStatus struct {
  1914  	machineId  string
  1915  	status     params.Status
  1916  	statusInfo string
  1917  }
  1918  
  1919  func (sms setMachineStatus) step(c *gc.C, ctx *context) {
  1920  	m, err := ctx.st.Machine(sms.machineId)
  1921  	c.Assert(err, gc.IsNil)
  1922  	err = m.SetStatus(sms.status, sms.statusInfo, nil)
  1923  	c.Assert(err, gc.IsNil)
  1924  }
  1925  
  1926  type relateServices struct {
  1927  	ep1, ep2 string
  1928  }
  1929  
  1930  func (rs relateServices) step(c *gc.C, ctx *context) {
  1931  	eps, err := ctx.st.InferEndpoints([]string{rs.ep1, rs.ep2})
  1932  	c.Assert(err, gc.IsNil)
  1933  	_, err = ctx.st.AddRelation(eps...)
  1934  	c.Assert(err, gc.IsNil)
  1935  }
  1936  
  1937  type addSubordinate struct {
  1938  	prinUnit   string
  1939  	subService string
  1940  }
  1941  
  1942  func (as addSubordinate) step(c *gc.C, ctx *context) {
  1943  	u, err := ctx.st.Unit(as.prinUnit)
  1944  	c.Assert(err, gc.IsNil)
  1945  	eps, err := ctx.st.InferEndpoints([]string{u.ServiceName(), as.subService})
  1946  	c.Assert(err, gc.IsNil)
  1947  	rel, err := ctx.st.EndpointsRelation(eps...)
  1948  	c.Assert(err, gc.IsNil)
  1949  	ru, err := rel.Unit(u)
  1950  	c.Assert(err, gc.IsNil)
  1951  	err = ru.EnterScope(nil)
  1952  	c.Assert(err, gc.IsNil)
  1953  }
  1954  
  1955  type scopedExpect struct {
  1956  	what   string
  1957  	scope  []string
  1958  	output M
  1959  }
  1960  
  1961  type expect struct {
  1962  	what   string
  1963  	output M
  1964  }
  1965  
  1966  func (e scopedExpect) step(c *gc.C, ctx *context) {
  1967  	c.Logf("\nexpect: %s %s\n", e.what, strings.Join(e.scope, " "))
  1968  
  1969  	// Now execute the command for each format.
  1970  	for _, format := range statusFormats {
  1971  		c.Logf("format %q", format.name)
  1972  		// Run command with the required format.
  1973  		args := append([]string{"--format", format.name}, e.scope...)
  1974  		code, stdout, stderr := runStatus(c, args...)
  1975  		c.Assert(code, gc.Equals, 0)
  1976  		if !c.Check(stderr, gc.HasLen, 0) {
  1977  			c.Fatalf("status failed: %s", string(stderr))
  1978  		}
  1979  
  1980  		// Prepare the output in the same format.
  1981  		buf, err := format.marshal(e.output)
  1982  		c.Assert(err, gc.IsNil)
  1983  		expected := make(M)
  1984  		err = format.unmarshal(buf, &expected)
  1985  		c.Assert(err, gc.IsNil)
  1986  
  1987  		// Check the output is as expected.
  1988  		actual := make(M)
  1989  		err = format.unmarshal(stdout, &actual)
  1990  		c.Assert(err, gc.IsNil)
  1991  		c.Assert(actual, jc.DeepEquals, expected)
  1992  	}
  1993  }
  1994  
  1995  func (e expect) step(c *gc.C, ctx *context) {
  1996  	scopedExpect{e.what, nil, e.output}.step(c, ctx)
  1997  }
  1998  
  1999  func (s *StatusSuite) TestStatusAllFormats(c *gc.C) {
  2000  	for i, t := range statusTests {
  2001  		c.Logf("test %d: %s", i, t.summary)
  2002  		func() {
  2003  			// Prepare context and run all steps to setup.
  2004  			ctx := s.newContext()
  2005  			defer s.resetContext(c, ctx)
  2006  			ctx.run(c, t.steps)
  2007  		}()
  2008  	}
  2009  }
  2010  
  2011  func (s *StatusSuite) TestStatusFilterErrors(c *gc.C) {
  2012  	steps := []stepper{
  2013  		addMachine{machineId: "0", job: state.JobManageEnviron},
  2014  		addMachine{machineId: "1", job: state.JobHostUnits},
  2015  		addCharm{"mysql"},
  2016  		addService{name: "mysql", charm: "mysql"},
  2017  		addAliveUnit{"mysql", "1"},
  2018  	}
  2019  	ctx := s.newContext()
  2020  	defer s.resetContext(c, ctx)
  2021  	ctx.run(c, steps)
  2022  
  2023  	// Status filters can only fail if the patterns are invalid.
  2024  	code, _, stderr := runStatus(c, "[*")
  2025  	c.Assert(code, gc.Not(gc.Equals), 0)
  2026  	c.Assert(string(stderr), gc.Equals, `error: pattern "[*" contains invalid characters`+"\n")
  2027  
  2028  	code, _, stderr = runStatus(c, "//")
  2029  	c.Assert(code, gc.Not(gc.Equals), 0)
  2030  	c.Assert(string(stderr), gc.Equals, `error: pattern "//" contains too many '/' characters`+"\n")
  2031  
  2032  	// Pattern validity is checked eagerly; if a bad pattern
  2033  	// proceeds a valid, matching pattern, then the bad pattern
  2034  	// will still cause an error.
  2035  	code, _, stderr = runStatus(c, "*", "[*")
  2036  	c.Assert(code, gc.Not(gc.Equals), 0)
  2037  	c.Assert(string(stderr), gc.Equals, `error: pattern "[*" contains invalid characters`+"\n")
  2038  }