github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/status/status_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package status
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"os"
    11  	"regexp"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/juju/cmd"
    16  	jc "github.com/juju/testing/checkers"
    17  	gc "gopkg.in/check.v1"
    18  	"gopkg.in/juju/charm.v5"
    19  	goyaml "gopkg.in/yaml.v1"
    20  
    21  	"github.com/juju/juju/apiserver/params"
    22  	"github.com/juju/juju/cmd/envcmd"
    23  	"github.com/juju/juju/constraints"
    24  	"github.com/juju/juju/environs"
    25  	"github.com/juju/juju/instance"
    26  	"github.com/juju/juju/juju/osenv"
    27  	"github.com/juju/juju/juju/testing"
    28  	"github.com/juju/juju/network"
    29  	"github.com/juju/juju/state"
    30  	"github.com/juju/juju/state/multiwatcher"
    31  	"github.com/juju/juju/state/presence"
    32  	"github.com/juju/juju/testcharms"
    33  	coretesting "github.com/juju/juju/testing"
    34  	"github.com/juju/juju/version"
    35  )
    36  
    37  func runStatus(c *gc.C, args ...string) (code int, stdout, stderr []byte) {
    38  	ctx := coretesting.Context(c)
    39  	code = cmd.Main(envcmd.Wrap(&StatusCommand{}), ctx, args)
    40  	stdout = ctx.Stdout.(*bytes.Buffer).Bytes()
    41  	stderr = ctx.Stderr.(*bytes.Buffer).Bytes()
    42  	return
    43  }
    44  
    45  type StatusSuite struct {
    46  	testing.JujuConnSuite
    47  }
    48  
    49  var _ = gc.Suite(&StatusSuite{})
    50  
    51  type M map[string]interface{}
    52  
    53  type L []interface{}
    54  
    55  type testCase struct {
    56  	summary string
    57  	steps   []stepper
    58  }
    59  
    60  func test(summary string, steps ...stepper) testCase {
    61  	return testCase{summary, steps}
    62  }
    63  
    64  type stepper interface {
    65  	step(c *gc.C, ctx *context)
    66  }
    67  
    68  //
    69  // context
    70  //
    71  
    72  func newContext(c *gc.C, st *state.State, env environs.Environ, adminUserTag string) *context {
    73  	// We make changes in the API server's state so that
    74  	// our changes to presence are immediately noticed
    75  	// in the status.
    76  	return &context{
    77  		st:           st,
    78  		env:          env,
    79  		charms:       make(map[string]*state.Charm),
    80  		pingers:      make(map[string]*presence.Pinger),
    81  		adminUserTag: adminUserTag,
    82  	}
    83  }
    84  
    85  type context struct {
    86  	st            *state.State
    87  	env           environs.Environ
    88  	charms        map[string]*state.Charm
    89  	pingers       map[string]*presence.Pinger
    90  	adminUserTag  string // A string repr of the tag.
    91  	expectIsoTime bool
    92  }
    93  
    94  func (ctx *context) reset(c *gc.C) {
    95  	for _, up := range ctx.pingers {
    96  		err := up.Kill()
    97  		c.Check(err, jc.ErrorIsNil)
    98  	}
    99  }
   100  
   101  func (ctx *context) run(c *gc.C, steps []stepper) {
   102  	for i, s := range steps {
   103  		c.Logf("step %d", i)
   104  		c.Logf("%#v", s)
   105  		s.step(c, ctx)
   106  	}
   107  }
   108  
   109  func (ctx *context) setAgentPresence(c *gc.C, p presence.Presencer) *presence.Pinger {
   110  	pinger, err := p.SetAgentPresence()
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	ctx.st.StartSync()
   113  	err = p.WaitAgentPresence(coretesting.LongWait)
   114  	c.Assert(err, jc.ErrorIsNil)
   115  	agentPresence, err := p.AgentPresence()
   116  	c.Assert(err, jc.ErrorIsNil)
   117  	c.Assert(agentPresence, jc.IsTrue)
   118  	return pinger
   119  }
   120  
   121  func (s *StatusSuite) newContext(c *gc.C) *context {
   122  	st := s.Environ.(testing.GetStater).GetStateInAPIServer()
   123  	// We make changes in the API server's state so that
   124  	// our changes to presence are immediately noticed
   125  	// in the status.
   126  	return newContext(c, st, s.Environ, s.AdminUserTag(c).String())
   127  }
   128  
   129  func (s *StatusSuite) resetContext(c *gc.C, ctx *context) {
   130  	ctx.reset(c)
   131  	s.JujuConnSuite.Reset(c)
   132  }
   133  
   134  // shortcuts for expected output.
   135  var (
   136  	machine0 = M{
   137  		"agent-state":                "started",
   138  		"dns-name":                   "dummyenv-0.dns",
   139  		"instance-id":                "dummyenv-0",
   140  		"series":                     "quantal",
   141  		"hardware":                   "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   142  		"state-server-member-status": "adding-vote",
   143  	}
   144  	machine1 = M{
   145  		"agent-state": "started",
   146  		"dns-name":    "dummyenv-1.dns",
   147  		"instance-id": "dummyenv-1",
   148  		"series":      "quantal",
   149  		"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   150  	}
   151  	machine2 = M{
   152  		"agent-state": "started",
   153  		"dns-name":    "dummyenv-2.dns",
   154  		"instance-id": "dummyenv-2",
   155  		"series":      "quantal",
   156  		"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   157  	}
   158  	machine3 = M{
   159  		"agent-state": "started",
   160  		"dns-name":    "dummyenv-3.dns",
   161  		"instance-id": "dummyenv-3",
   162  		"series":      "quantal",
   163  		"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   164  	}
   165  	machine4 = M{
   166  		"agent-state": "started",
   167  		"dns-name":    "dummyenv-4.dns",
   168  		"instance-id": "dummyenv-4",
   169  		"series":      "quantal",
   170  		"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   171  	}
   172  	machine1WithContainers = M{
   173  		"agent-state": "started",
   174  		"containers": M{
   175  			"1/lxc/0": M{
   176  				"agent-state": "started",
   177  				"containers": M{
   178  					"1/lxc/0/lxc/0": M{
   179  						"agent-state": "started",
   180  						"dns-name":    "dummyenv-3.dns",
   181  						"instance-id": "dummyenv-3",
   182  						"series":      "quantal",
   183  					},
   184  				},
   185  				"dns-name":    "dummyenv-2.dns",
   186  				"instance-id": "dummyenv-2",
   187  				"series":      "quantal",
   188  			},
   189  			"1/lxc/1": M{
   190  				"instance-id": "pending",
   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  	machine1WithContainersScoped = M{
   200  		"agent-state": "started",
   201  		"containers": M{
   202  			"1/lxc/0": M{
   203  				"agent-state": "started",
   204  				"dns-name":    "dummyenv-2.dns",
   205  				"instance-id": "dummyenv-2",
   206  				"series":      "quantal",
   207  			},
   208  		},
   209  		"dns-name":    "dummyenv-1.dns",
   210  		"instance-id": "dummyenv-1",
   211  		"series":      "quantal",
   212  		"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   213  	}
   214  	unexposedService = M{
   215  		"service-status": M{
   216  			"current": "unknown",
   217  			"message": "Waiting for agent initialization to finish",
   218  			"since":   "01 Apr 15 01:23+10:00",
   219  		},
   220  		"charm":   "cs:quantal/dummy-1",
   221  		"exposed": false,
   222  	}
   223  	exposedService = M{
   224  		"service-status": M{
   225  			"current": "unknown",
   226  			"message": "Waiting for agent initialization to finish",
   227  			"since":   "01 Apr 15 01:23+10:00",
   228  		},
   229  		"charm":   "cs:quantal/dummy-1",
   230  		"exposed": true,
   231  	}
   232  )
   233  
   234  type outputFormat struct {
   235  	name      string
   236  	marshal   func(v interface{}) ([]byte, error)
   237  	unmarshal func(data []byte, v interface{}) error
   238  }
   239  
   240  // statusFormats list all output formats that can be marshalled as structured data,
   241  // supported by status command.
   242  var statusFormats = []outputFormat{
   243  	{"yaml", goyaml.Marshal, goyaml.Unmarshal},
   244  	{"json", json.Marshal, json.Unmarshal},
   245  }
   246  
   247  var machineCons = constraints.MustParse("cpu-cores=2 mem=8G root-disk=8G")
   248  
   249  var statusTests = []testCase{
   250  	// Status tests
   251  	test(
   252  		"bootstrap and starting a single instance",
   253  
   254  		addMachine{machineId: "0", job: state.JobManageEnviron},
   255  		expect{
   256  			"simulate juju bootstrap by adding machine/0 to the state",
   257  			M{
   258  				"environment": "dummyenv",
   259  				"machines": M{
   260  					"0": M{
   261  						"instance-id":                "pending",
   262  						"series":                     "quantal",
   263  						"state-server-member-status": "adding-vote",
   264  					},
   265  				},
   266  				"services": M{},
   267  			},
   268  		},
   269  
   270  		startAliveMachine{"0"},
   271  		setAddresses{"0", []network.Address{
   272  			network.NewAddress("10.0.0.1"),
   273  			network.NewScopedAddress("dummyenv-0.dns", network.ScopePublic),
   274  		}},
   275  		expect{
   276  			"simulate the PA starting an instance in response to the state change",
   277  			M{
   278  				"environment": "dummyenv",
   279  				"machines": M{
   280  					"0": M{
   281  						"agent-state":                "pending",
   282  						"dns-name":                   "dummyenv-0.dns",
   283  						"instance-id":                "dummyenv-0",
   284  						"series":                     "quantal",
   285  						"hardware":                   "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   286  						"state-server-member-status": "adding-vote",
   287  					},
   288  				},
   289  				"services": M{},
   290  			},
   291  		},
   292  
   293  		setMachineStatus{"0", state.StatusStarted, ""},
   294  		expect{
   295  			"simulate the MA started and set the machine status",
   296  			M{
   297  				"environment": "dummyenv",
   298  				"machines": M{
   299  					"0": machine0,
   300  				},
   301  				"services": M{},
   302  			},
   303  		},
   304  
   305  		setTools{"0", version.MustParseBinary("1.2.3-trusty-ppc")},
   306  		expect{
   307  			"simulate the MA setting the version",
   308  			M{
   309  				"environment": "dummyenv",
   310  				"machines": M{
   311  					"0": M{
   312  						"dns-name":                   "dummyenv-0.dns",
   313  						"instance-id":                "dummyenv-0",
   314  						"agent-version":              "1.2.3",
   315  						"agent-state":                "started",
   316  						"series":                     "quantal",
   317  						"hardware":                   "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   318  						"state-server-member-status": "adding-vote",
   319  					},
   320  				},
   321  				"services": M{},
   322  			},
   323  		},
   324  	), test(
   325  		"instance with different hardware characteristics",
   326  		addMachine{machineId: "0", cons: machineCons, job: state.JobManageEnviron},
   327  		setAddresses{"0", []network.Address{
   328  			network.NewAddress("10.0.0.1"),
   329  			network.NewScopedAddress("dummyenv-0.dns", network.ScopePublic),
   330  		}},
   331  		startAliveMachine{"0"},
   332  		setMachineStatus{"0", state.StatusStarted, ""},
   333  		expect{
   334  			"machine 0 has specific hardware characteristics",
   335  			M{
   336  				"environment": "dummyenv",
   337  				"machines": M{
   338  					"0": M{
   339  						"agent-state":                "started",
   340  						"dns-name":                   "dummyenv-0.dns",
   341  						"instance-id":                "dummyenv-0",
   342  						"series":                     "quantal",
   343  						"hardware":                   "arch=amd64 cpu-cores=2 mem=8192M root-disk=8192M",
   344  						"state-server-member-status": "adding-vote",
   345  					},
   346  				},
   347  				"services": M{},
   348  			},
   349  		},
   350  	), test(
   351  		"instance without addresses",
   352  		addMachine{machineId: "0", cons: machineCons, job: state.JobManageEnviron},
   353  		startAliveMachine{"0"},
   354  		setMachineStatus{"0", state.StatusStarted, ""},
   355  		expect{
   356  			"machine 0 has no dns-name",
   357  			M{
   358  				"environment": "dummyenv",
   359  				"machines": M{
   360  					"0": M{
   361  						"agent-state":                "started",
   362  						"instance-id":                "dummyenv-0",
   363  						"series":                     "quantal",
   364  						"hardware":                   "arch=amd64 cpu-cores=2 mem=8192M root-disk=8192M",
   365  						"state-server-member-status": "adding-vote",
   366  					},
   367  				},
   368  				"services": M{},
   369  			},
   370  		},
   371  	), test(
   372  		"test pending and missing machines",
   373  		addMachine{machineId: "0", job: state.JobManageEnviron},
   374  		expect{
   375  			"machine 0 reports pending",
   376  			M{
   377  				"environment": "dummyenv",
   378  				"machines": M{
   379  					"0": M{
   380  						"instance-id":                "pending",
   381  						"series":                     "quantal",
   382  						"state-server-member-status": "adding-vote",
   383  					},
   384  				},
   385  				"services": M{},
   386  			},
   387  		},
   388  
   389  		startMissingMachine{"0"},
   390  		expect{
   391  			"machine 0 reports missing",
   392  			M{
   393  				"environment": "dummyenv",
   394  				"machines": M{
   395  					"0": M{
   396  						"instance-state":             "missing",
   397  						"instance-id":                "i-missing",
   398  						"agent-state":                "pending",
   399  						"series":                     "quantal",
   400  						"hardware":                   "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   401  						"state-server-member-status": "adding-vote",
   402  					},
   403  				},
   404  				"services": M{},
   405  			},
   406  		},
   407  	), test(
   408  		"add two services and expose one, then add 2 more machines and some units",
   409  		addMachine{machineId: "0", job: state.JobManageEnviron},
   410  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
   411  		startAliveMachine{"0"},
   412  		setMachineStatus{"0", state.StatusStarted, ""},
   413  		addCharm{"dummy"},
   414  		addService{name: "dummy-service", charm: "dummy"},
   415  		addService{name: "exposed-service", charm: "dummy"},
   416  		expect{
   417  			"no services exposed yet",
   418  			M{
   419  				"environment": "dummyenv",
   420  				"machines": M{
   421  					"0": machine0,
   422  				},
   423  				"services": M{
   424  					"dummy-service":   unexposedService,
   425  					"exposed-service": unexposedService,
   426  				},
   427  			},
   428  		},
   429  
   430  		setServiceExposed{"exposed-service", true},
   431  		expect{
   432  			"one exposed service",
   433  			M{
   434  				"environment": "dummyenv",
   435  				"machines": M{
   436  					"0": machine0,
   437  				},
   438  				"services": M{
   439  					"dummy-service":   unexposedService,
   440  					"exposed-service": exposedService,
   441  				},
   442  			},
   443  		},
   444  
   445  		addMachine{machineId: "1", job: state.JobHostUnits},
   446  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
   447  		startAliveMachine{"1"},
   448  		setMachineStatus{"1", state.StatusStarted, ""},
   449  		addMachine{machineId: "2", job: state.JobHostUnits},
   450  		setAddresses{"2", network.NewAddresses("dummyenv-2.dns")},
   451  		startAliveMachine{"2"},
   452  		setMachineStatus{"2", state.StatusStarted, ""},
   453  		expect{
   454  			"two more machines added",
   455  			M{
   456  				"environment": "dummyenv",
   457  				"machines": M{
   458  					"0": machine0,
   459  					"1": machine1,
   460  					"2": machine2,
   461  				},
   462  				"services": M{
   463  					"dummy-service":   unexposedService,
   464  					"exposed-service": exposedService,
   465  				},
   466  			},
   467  		},
   468  
   469  		addAliveUnit{"dummy-service", "1"},
   470  		addAliveUnit{"exposed-service", "2"},
   471  		setAgentStatus{"exposed-service/0", state.StatusError, "You Require More Vespene Gas", nil},
   472  		// Open multiple ports with different protocols,
   473  		// ensure they're sorted on protocol, then number.
   474  		openUnitPort{"exposed-service/0", "udp", 10},
   475  		openUnitPort{"exposed-service/0", "udp", 2},
   476  		openUnitPort{"exposed-service/0", "tcp", 3},
   477  		openUnitPort{"exposed-service/0", "tcp", 2},
   478  		// Simulate some status with no info, while the agent is down.
   479  		// Status used to be down, we no longer support said state.
   480  		// now is one of: pending, started, error.
   481  		setUnitStatus{"dummy-service/0", state.StatusTerminated, "", nil},
   482  		setAgentStatus{"dummy-service/0", state.StatusIdle, "", nil},
   483  
   484  		// dummy-service/0 used to expect "agent-state-info": "(started)",
   485  		// which is populated as the previous state by adjustInfoIfAgentDown
   486  		// but sice it no longer is down it no longer applies.
   487  		expect{
   488  			"add two units, one alive (in error state), one started",
   489  			M{
   490  				"environment": "dummyenv",
   491  				"machines": M{
   492  					"0": machine0,
   493  					"1": machine1,
   494  					"2": machine2,
   495  				},
   496  				"services": M{
   497  					"exposed-service": M{
   498  						"charm":   "cs:quantal/dummy-1",
   499  						"exposed": true,
   500  						"service-status": M{
   501  							"current": "error",
   502  							"message": "You Require More Vespene Gas",
   503  							"since":   "01 Apr 15 01:23+10:00",
   504  						},
   505  						"units": M{
   506  							"exposed-service/0": M{
   507  								"machine":          "2",
   508  								"agent-state":      "error",
   509  								"agent-state-info": "You Require More Vespene Gas",
   510  								"workload-status": M{
   511  									"current": "error",
   512  									"message": "You Require More Vespene Gas",
   513  									"since":   "01 Apr 15 01:23+10:00",
   514  								},
   515  								"agent-status": M{
   516  									"current": "idle",
   517  									"since":   "01 Apr 15 01:23+10:00",
   518  								},
   519  								"open-ports": L{
   520  									"2/tcp", "3/tcp", "2/udp", "10/udp",
   521  								},
   522  								"public-address": "dummyenv-2.dns",
   523  							},
   524  						},
   525  					},
   526  					"dummy-service": M{
   527  						"charm":   "cs:quantal/dummy-1",
   528  						"exposed": false,
   529  						"service-status": M{
   530  							"current": "terminated",
   531  							"since":   "01 Apr 15 01:23+10:00",
   532  						},
   533  						"units": M{
   534  							"dummy-service/0": M{
   535  								"machine":     "1",
   536  								"agent-state": "stopped",
   537  								"workload-status": M{
   538  									"current": "terminated",
   539  									"since":   "01 Apr 15 01:23+10:00",
   540  								},
   541  								"agent-status": M{
   542  									"current": "idle",
   543  									"since":   "01 Apr 15 01:23+10:00",
   544  								},
   545  								"public-address": "dummyenv-1.dns",
   546  							},
   547  						},
   548  					},
   549  				},
   550  			},
   551  		},
   552  
   553  		addMachine{machineId: "3", job: state.JobHostUnits},
   554  		startMachine{"3"},
   555  		// Simulate some status with info, while the agent is down.
   556  		setAddresses{"3", network.NewAddresses("dummyenv-3.dns")},
   557  		setMachineStatus{"3", state.StatusStopped, "Really?"},
   558  		addMachine{machineId: "4", job: state.JobHostUnits},
   559  		setAddresses{"4", network.NewAddresses("dummyenv-4.dns")},
   560  		startAliveMachine{"4"},
   561  		setMachineStatus{"4", state.StatusError, "Beware the red toys"},
   562  		ensureDyingUnit{"dummy-service/0"},
   563  		addMachine{machineId: "5", job: state.JobHostUnits},
   564  		ensureDeadMachine{"5"},
   565  		expect{
   566  			"add three more machine, one with a dead agent, one in error state and one dead itself; also one dying unit",
   567  			M{
   568  				"environment": "dummyenv",
   569  				"machines": M{
   570  					"0": machine0,
   571  					"1": machine1,
   572  					"2": machine2,
   573  					"3": M{
   574  						"dns-name":         "dummyenv-3.dns",
   575  						"instance-id":      "dummyenv-3",
   576  						"agent-state":      "down",
   577  						"agent-state-info": "(stopped: Really?)",
   578  						"series":           "quantal",
   579  						"hardware":         "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   580  					},
   581  					"4": M{
   582  						"dns-name":         "dummyenv-4.dns",
   583  						"instance-id":      "dummyenv-4",
   584  						"agent-state":      "error",
   585  						"agent-state-info": "Beware the red toys",
   586  						"series":           "quantal",
   587  						"hardware":         "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
   588  					},
   589  					"5": M{
   590  						"life":        "dead",
   591  						"instance-id": "pending",
   592  						"series":      "quantal",
   593  					},
   594  				},
   595  				"services": M{
   596  					"exposed-service": M{
   597  						"charm":   "cs:quantal/dummy-1",
   598  						"exposed": true,
   599  						"service-status": M{
   600  							"current": "error",
   601  							"message": "You Require More Vespene Gas",
   602  							"since":   "01 Apr 15 01:23+10:00",
   603  						},
   604  						"units": M{
   605  							"exposed-service/0": M{
   606  								"machine":          "2",
   607  								"agent-state":      "error",
   608  								"agent-state-info": "You Require More Vespene Gas",
   609  								"workload-status": M{
   610  									"current": "error",
   611  									"message": "You Require More Vespene Gas",
   612  									"since":   "01 Apr 15 01:23+10:00",
   613  								},
   614  								"agent-status": M{
   615  									"current": "idle",
   616  									"since":   "01 Apr 15 01:23+10:00",
   617  								},
   618  								"open-ports": L{
   619  									"2/tcp", "3/tcp", "2/udp", "10/udp",
   620  								},
   621  								"public-address": "dummyenv-2.dns",
   622  							},
   623  						},
   624  					},
   625  					"dummy-service": M{
   626  						"charm":   "cs:quantal/dummy-1",
   627  						"exposed": false,
   628  						"service-status": M{
   629  							"current": "terminated",
   630  							"since":   "01 Apr 15 01:23+10:00",
   631  						},
   632  						"units": M{
   633  							"dummy-service/0": M{
   634  								"machine":     "1",
   635  								"agent-state": "stopped",
   636  								"life":        "dying",
   637  								"workload-status": M{
   638  									"current": "terminated",
   639  									"since":   "01 Apr 15 01:23+10:00",
   640  								},
   641  								"agent-status": M{
   642  									"current": "idle",
   643  									"since":   "01 Apr 15 01:23+10:00",
   644  								},
   645  								"public-address": "dummyenv-1.dns",
   646  							},
   647  						},
   648  					},
   649  				},
   650  			},
   651  		},
   652  
   653  		scopedExpect{
   654  			"scope status on dummy-service/0 unit",
   655  			[]string{"dummy-service/0"},
   656  			M{
   657  				"environment": "dummyenv",
   658  				"machines": M{
   659  					"1": machine1,
   660  				},
   661  				"services": M{
   662  					"dummy-service": M{
   663  						"charm":   "cs:quantal/dummy-1",
   664  						"exposed": false,
   665  						"service-status": M{
   666  							"current": "terminated",
   667  							"since":   "01 Apr 15 01:23+10:00",
   668  						},
   669  						"units": M{
   670  							"dummy-service/0": M{
   671  								"machine":     "1",
   672  								"life":        "dying",
   673  								"agent-state": "stopped",
   674  								"workload-status": M{
   675  									"current": "terminated",
   676  									"since":   "01 Apr 15 01:23+10:00",
   677  								},
   678  								"agent-status": M{
   679  									"current": "idle",
   680  									"since":   "01 Apr 15 01:23+10:00",
   681  								},
   682  								"public-address": "dummyenv-1.dns",
   683  							},
   684  						},
   685  					},
   686  				},
   687  			},
   688  		},
   689  		scopedExpect{
   690  			"scope status on exposed-service service",
   691  			[]string{"exposed-service"},
   692  			M{
   693  				"environment": "dummyenv",
   694  				"machines": M{
   695  					"2": machine2,
   696  				},
   697  				"services": M{
   698  					"exposed-service": M{
   699  						"charm":   "cs:quantal/dummy-1",
   700  						"exposed": true,
   701  						"service-status": M{
   702  							"current": "error",
   703  							"message": "You Require More Vespene Gas",
   704  							"since":   "01 Apr 15 01:23+10:00",
   705  						},
   706  						"units": M{
   707  							"exposed-service/0": M{
   708  								"machine":          "2",
   709  								"agent-state":      "error",
   710  								"agent-state-info": "You Require More Vespene Gas",
   711  								"workload-status": M{
   712  									"current": "error",
   713  									"message": "You Require More Vespene Gas",
   714  									"since":   "01 Apr 15 01:23+10:00",
   715  								},
   716  								"agent-status": M{
   717  									"current": "idle",
   718  									"since":   "01 Apr 15 01:23+10:00",
   719  								},
   720  								"open-ports": L{
   721  									"2/tcp", "3/tcp", "2/udp", "10/udp",
   722  								},
   723  								"public-address": "dummyenv-2.dns",
   724  							},
   725  						},
   726  					},
   727  				},
   728  			},
   729  		},
   730  		scopedExpect{
   731  			"scope status on service pattern",
   732  			[]string{"d*-service"},
   733  			M{
   734  				"environment": "dummyenv",
   735  				"machines": M{
   736  					"1": machine1,
   737  				},
   738  				"services": M{
   739  					"dummy-service": M{
   740  						"charm":   "cs:quantal/dummy-1",
   741  						"exposed": false,
   742  						"service-status": M{
   743  							"current": "terminated",
   744  							"since":   "01 Apr 15 01:23+10:00",
   745  						},
   746  						"units": M{
   747  							"dummy-service/0": M{
   748  								"machine":     "1",
   749  								"life":        "dying",
   750  								"agent-state": "stopped",
   751  								"workload-status": M{
   752  									"current": "terminated",
   753  									"since":   "01 Apr 15 01:23+10:00",
   754  								},
   755  								"agent-status": M{
   756  									"current": "idle",
   757  									"since":   "01 Apr 15 01:23+10:00",
   758  								},
   759  								"public-address": "dummyenv-1.dns",
   760  							},
   761  						},
   762  					},
   763  				},
   764  			},
   765  		},
   766  		scopedExpect{
   767  			"scope status on unit pattern",
   768  			[]string{"e*posed-service/*"},
   769  			M{
   770  				"environment": "dummyenv",
   771  				"machines": M{
   772  					"2": machine2,
   773  				},
   774  				"services": M{
   775  					"exposed-service": M{
   776  						"charm":   "cs:quantal/dummy-1",
   777  						"exposed": true,
   778  						"service-status": M{
   779  							"current": "error",
   780  							"message": "You Require More Vespene Gas",
   781  							"since":   "01 Apr 15 01:23+10:00",
   782  						},
   783  						"units": M{
   784  							"exposed-service/0": M{
   785  								"machine":          "2",
   786  								"agent-state":      "error",
   787  								"agent-state-info": "You Require More Vespene Gas",
   788  								"workload-status": M{
   789  									"current": "error",
   790  									"message": "You Require More Vespene Gas",
   791  									"since":   "01 Apr 15 01:23+10:00",
   792  								},
   793  								"agent-status": M{
   794  									"current": "idle",
   795  									"since":   "01 Apr 15 01:23+10:00",
   796  								},
   797  								"open-ports": L{
   798  									"2/tcp", "3/tcp", "2/udp", "10/udp",
   799  								},
   800  								"public-address": "dummyenv-2.dns",
   801  							},
   802  						},
   803  					},
   804  				},
   805  			},
   806  		},
   807  		scopedExpect{
   808  			"scope status on combination of service and unit patterns",
   809  			[]string{"exposed-service", "dummy-service", "e*posed-service/*", "dummy-service/*"},
   810  			M{
   811  				"environment": "dummyenv",
   812  				"machines": M{
   813  					"1": machine1,
   814  					"2": machine2,
   815  				},
   816  				"services": M{
   817  					"dummy-service": M{
   818  						"charm":   "cs:quantal/dummy-1",
   819  						"exposed": false,
   820  						"service-status": M{
   821  							"current": "terminated",
   822  							"since":   "01 Apr 15 01:23+10:00",
   823  						},
   824  						"units": M{
   825  							"dummy-service/0": M{
   826  								"machine":     "1",
   827  								"life":        "dying",
   828  								"agent-state": "stopped",
   829  								"workload-status": M{
   830  									"current": "terminated",
   831  									"since":   "01 Apr 15 01:23+10:00",
   832  								},
   833  								"agent-status": M{
   834  									"current": "idle",
   835  									"since":   "01 Apr 15 01:23+10:00",
   836  								},
   837  								"public-address": "dummyenv-1.dns",
   838  							},
   839  						},
   840  					},
   841  					"exposed-service": M{
   842  						"charm":   "cs:quantal/dummy-1",
   843  						"exposed": true,
   844  						"service-status": M{
   845  							"current": "error",
   846  							"message": "You Require More Vespene Gas",
   847  							"since":   "01 Apr 15 01:23+10:00",
   848  						},
   849  						"units": M{
   850  							"exposed-service/0": M{
   851  								"machine":          "2",
   852  								"agent-state":      "error",
   853  								"agent-state-info": "You Require More Vespene Gas",
   854  								"workload-status": M{
   855  									"current": "error",
   856  									"message": "You Require More Vespene Gas",
   857  									"since":   "01 Apr 15 01:23+10:00",
   858  								},
   859  								"agent-status": M{
   860  									"current": "idle",
   861  									"since":   "01 Apr 15 01:23+10:00",
   862  								},
   863  								"open-ports": L{
   864  									"2/tcp", "3/tcp", "2/udp", "10/udp",
   865  								},
   866  								"public-address": "dummyenv-2.dns",
   867  							},
   868  						},
   869  					},
   870  				},
   871  			},
   872  		},
   873  	), test(
   874  		"a unit with a hook relation error",
   875  		addMachine{machineId: "0", job: state.JobManageEnviron},
   876  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
   877  		startAliveMachine{"0"},
   878  		setMachineStatus{"0", state.StatusStarted, ""},
   879  
   880  		addMachine{machineId: "1", job: state.JobHostUnits},
   881  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
   882  		startAliveMachine{"1"},
   883  		setMachineStatus{"1", state.StatusStarted, ""},
   884  
   885  		addCharm{"wordpress"},
   886  		addService{name: "wordpress", charm: "wordpress"},
   887  		addAliveUnit{"wordpress", "1"},
   888  
   889  		addCharm{"mysql"},
   890  		addService{name: "mysql", charm: "mysql"},
   891  		addAliveUnit{"mysql", "1"},
   892  
   893  		relateServices{"wordpress", "mysql"},
   894  
   895  		setAgentStatus{"wordpress/0", state.StatusError,
   896  			"hook failed: some-relation-changed",
   897  			map[string]interface{}{"relation-id": 0}},
   898  
   899  		expect{
   900  			"a unit with a hook relation error",
   901  			M{
   902  				"environment": "dummyenv",
   903  				"machines": M{
   904  					"0": machine0,
   905  					"1": machine1,
   906  				},
   907  				"services": M{
   908  					"wordpress": M{
   909  						"charm":   "cs:quantal/wordpress-3",
   910  						"exposed": false,
   911  						"relations": M{
   912  							"db": L{"mysql"},
   913  						},
   914  						"service-status": M{
   915  							"current": "error",
   916  							"message": "hook failed: some-relation-changed",
   917  							"since":   "01 Apr 15 01:23+10:00",
   918  						},
   919  						"units": M{
   920  							"wordpress/0": M{
   921  								"machine":          "1",
   922  								"agent-state":      "error",
   923  								"agent-state-info": "hook failed: some-relation-changed for mysql:server",
   924  								"workload-status": M{
   925  									"current": "error",
   926  									"message": "hook failed: some-relation-changed for mysql:server",
   927  									"since":   "01 Apr 15 01:23+10:00",
   928  								},
   929  								"agent-status": M{
   930  									"current": "idle",
   931  									"since":   "01 Apr 15 01:23+10:00",
   932  								},
   933  								"public-address": "dummyenv-1.dns",
   934  							},
   935  						},
   936  					},
   937  					"mysql": M{
   938  						"charm":   "cs:quantal/mysql-1",
   939  						"exposed": false,
   940  						"relations": M{
   941  							"server": L{"wordpress"},
   942  						},
   943  						"service-status": M{
   944  							"current": "unknown",
   945  							"message": "Waiting for agent initialization to finish",
   946  							"since":   "01 Apr 15 01:23+10:00",
   947  						},
   948  						"units": M{
   949  							"mysql/0": M{
   950  								"machine":     "1",
   951  								"agent-state": "pending",
   952  								"workload-status": M{
   953  									"current": "unknown",
   954  									"message": "Waiting for agent initialization to finish",
   955  									"since":   "01 Apr 15 01:23+10:00",
   956  								},
   957  								"agent-status": M{
   958  									"current": "allocating",
   959  									"since":   "01 Apr 15 01:23+10:00",
   960  								},
   961  								"public-address": "dummyenv-1.dns",
   962  							},
   963  						},
   964  					},
   965  				},
   966  			},
   967  		},
   968  	), test(
   969  		"a unit with a hook relation error when the agent is down",
   970  		addMachine{machineId: "0", job: state.JobManageEnviron},
   971  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
   972  		startAliveMachine{"0"},
   973  		setMachineStatus{"0", state.StatusStarted, ""},
   974  
   975  		addMachine{machineId: "1", job: state.JobHostUnits},
   976  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
   977  		startAliveMachine{"1"},
   978  		setMachineStatus{"1", state.StatusStarted, ""},
   979  
   980  		addCharm{"wordpress"},
   981  		addService{name: "wordpress", charm: "wordpress"},
   982  		addAliveUnit{"wordpress", "1"},
   983  
   984  		addCharm{"mysql"},
   985  		addService{name: "mysql", charm: "mysql"},
   986  		addAliveUnit{"mysql", "1"},
   987  
   988  		relateServices{"wordpress", "mysql"},
   989  
   990  		setAgentStatus{"wordpress/0", state.StatusError,
   991  			"hook failed: some-relation-changed",
   992  			map[string]interface{}{"relation-id": 0}},
   993  
   994  		expect{
   995  			"a unit with a hook relation error when the agent is down",
   996  			M{
   997  				"environment": "dummyenv",
   998  				"machines": M{
   999  					"0": machine0,
  1000  					"1": machine1,
  1001  				},
  1002  				"services": M{
  1003  					"wordpress": M{
  1004  						"charm":   "cs:quantal/wordpress-3",
  1005  						"exposed": false,
  1006  						"relations": M{
  1007  							"db": L{"mysql"},
  1008  						},
  1009  						"service-status": M{
  1010  							"current": "error",
  1011  							"message": "hook failed: some-relation-changed",
  1012  							"since":   "01 Apr 15 01:23+10:00",
  1013  						},
  1014  						"units": M{
  1015  							"wordpress/0": M{
  1016  								"machine":          "1",
  1017  								"agent-state":      "error",
  1018  								"agent-state-info": "hook failed: some-relation-changed for mysql:server",
  1019  								"workload-status": M{
  1020  									"current": "error",
  1021  									"message": "hook failed: some-relation-changed for mysql:server",
  1022  									"since":   "01 Apr 15 01:23+10:00",
  1023  								},
  1024  								"agent-status": M{
  1025  									"current": "idle",
  1026  									"since":   "01 Apr 15 01:23+10:00",
  1027  								},
  1028  								"public-address": "dummyenv-1.dns",
  1029  							},
  1030  						},
  1031  					},
  1032  					"mysql": M{
  1033  						"charm":   "cs:quantal/mysql-1",
  1034  						"exposed": false,
  1035  						"relations": M{
  1036  							"server": L{"wordpress"},
  1037  						},
  1038  						"service-status": M{
  1039  							"current": "unknown",
  1040  							"message": "Waiting for agent initialization to finish",
  1041  							"since":   "01 Apr 15 01:23+10:00",
  1042  						},
  1043  						"units": M{
  1044  							"mysql/0": M{
  1045  								"machine":     "1",
  1046  								"agent-state": "pending",
  1047  								"workload-status": M{
  1048  									"current": "unknown",
  1049  									"message": "Waiting for agent initialization to finish",
  1050  									"since":   "01 Apr 15 01:23+10:00",
  1051  								},
  1052  								"agent-status": M{
  1053  									"current": "allocating",
  1054  									"since":   "01 Apr 15 01:23+10:00",
  1055  								},
  1056  								"public-address": "dummyenv-1.dns",
  1057  							},
  1058  						},
  1059  					},
  1060  				},
  1061  			},
  1062  		},
  1063  	), test(
  1064  		"add a dying service",
  1065  		addCharm{"dummy"},
  1066  		addService{name: "dummy-service", charm: "dummy"},
  1067  		addMachine{machineId: "0", job: state.JobHostUnits},
  1068  		addAliveUnit{"dummy-service", "0"},
  1069  		ensureDyingService{"dummy-service"},
  1070  		expect{
  1071  			"service shows life==dying",
  1072  			M{
  1073  				"environment": "dummyenv",
  1074  				"machines": M{
  1075  					"0": M{
  1076  						"instance-id": "pending",
  1077  						"series":      "quantal",
  1078  					},
  1079  				},
  1080  				"services": M{
  1081  					"dummy-service": M{
  1082  						"charm":   "cs:quantal/dummy-1",
  1083  						"exposed": false,
  1084  						"life":    "dying",
  1085  						"service-status": M{
  1086  							"current": "unknown",
  1087  							"message": "Waiting for agent initialization to finish",
  1088  							"since":   "01 Apr 15 01:23+10:00",
  1089  						},
  1090  						"units": M{
  1091  							"dummy-service/0": M{
  1092  								"machine":     "0",
  1093  								"agent-state": "pending",
  1094  								"workload-status": M{
  1095  									"current": "unknown",
  1096  									"message": "Waiting for agent initialization to finish",
  1097  									"since":   "01 Apr 15 01:23+10:00",
  1098  								},
  1099  								"agent-status": M{
  1100  									"current": "allocating",
  1101  									"since":   "01 Apr 15 01:23+10:00",
  1102  								},
  1103  							},
  1104  						},
  1105  					},
  1106  				},
  1107  			},
  1108  		},
  1109  	), test(
  1110  		"a unit where the agent is down shows as lost",
  1111  		addCharm{"dummy"},
  1112  		addService{name: "dummy-service", charm: "dummy"},
  1113  		addMachine{machineId: "0", job: state.JobHostUnits},
  1114  		startAliveMachine{"0"},
  1115  		setMachineStatus{"0", state.StatusStarted, ""},
  1116  		addUnit{"dummy-service", "0"},
  1117  		setAgentStatus{"dummy-service/0", state.StatusIdle, "", nil},
  1118  		setUnitStatus{"dummy-service/0", state.StatusActive, "", nil},
  1119  		expect{
  1120  			"unit shows that agent is lost",
  1121  			M{
  1122  				"environment": "dummyenv",
  1123  				"machines": M{
  1124  					"0": M{
  1125  						"agent-state": "started",
  1126  						"instance-id": "dummyenv-0",
  1127  						"series":      "quantal",
  1128  						"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  1129  					},
  1130  				},
  1131  				"services": M{
  1132  					"dummy-service": M{
  1133  						"charm":   "cs:quantal/dummy-1",
  1134  						"exposed": false,
  1135  						"service-status": M{
  1136  							"current": "active",
  1137  							"since":   "01 Apr 15 01:23+10:00",
  1138  						},
  1139  						"units": M{
  1140  							"dummy-service/0": M{
  1141  								"machine":     "0",
  1142  								"agent-state": "started",
  1143  								"workload-status": M{
  1144  									"current": "unknown",
  1145  									"message": "agent is lost, sorry! See 'juju status-history dummy-service/0'",
  1146  									"since":   "01 Apr 15 01:23+10:00",
  1147  								},
  1148  								"agent-status": M{
  1149  									"current": "lost",
  1150  									"message": "agent is not communicating with the server",
  1151  									"since":   "01 Apr 15 01:23+10:00",
  1152  								},
  1153  							},
  1154  						},
  1155  					},
  1156  				},
  1157  			},
  1158  		},
  1159  	),
  1160  
  1161  	// Relation tests
  1162  	test(
  1163  		"complex scenario with multiple related services",
  1164  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1165  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  1166  		startAliveMachine{"0"},
  1167  		setMachineStatus{"0", state.StatusStarted, ""},
  1168  		addCharm{"wordpress"},
  1169  		addCharm{"mysql"},
  1170  		addCharm{"varnish"},
  1171  
  1172  		addService{name: "project", charm: "wordpress"},
  1173  		setServiceExposed{"project", true},
  1174  		addMachine{machineId: "1", job: state.JobHostUnits},
  1175  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  1176  		startAliveMachine{"1"},
  1177  		setMachineStatus{"1", state.StatusStarted, ""},
  1178  		addAliveUnit{"project", "1"},
  1179  		setAgentStatus{"project/0", state.StatusIdle, "", nil},
  1180  		setUnitStatus{"project/0", state.StatusActive, "", nil},
  1181  
  1182  		addService{name: "mysql", charm: "mysql"},
  1183  		setServiceExposed{"mysql", true},
  1184  		addMachine{machineId: "2", job: state.JobHostUnits},
  1185  		setAddresses{"2", network.NewAddresses("dummyenv-2.dns")},
  1186  		startAliveMachine{"2"},
  1187  		setMachineStatus{"2", state.StatusStarted, ""},
  1188  		addAliveUnit{"mysql", "2"},
  1189  		setAgentStatus{"mysql/0", state.StatusIdle, "", nil},
  1190  		setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  1191  
  1192  		addService{name: "varnish", charm: "varnish"},
  1193  		setServiceExposed{"varnish", true},
  1194  		addMachine{machineId: "3", job: state.JobHostUnits},
  1195  		setAddresses{"3", network.NewAddresses("dummyenv-3.dns")},
  1196  		startAliveMachine{"3"},
  1197  		setMachineStatus{"3", state.StatusStarted, ""},
  1198  		addAliveUnit{"varnish", "3"},
  1199  
  1200  		addService{name: "private", charm: "wordpress"},
  1201  		setServiceExposed{"private", true},
  1202  		addMachine{machineId: "4", job: state.JobHostUnits},
  1203  		setAddresses{"4", network.NewAddresses("dummyenv-4.dns")},
  1204  		startAliveMachine{"4"},
  1205  		setMachineStatus{"4", state.StatusStarted, ""},
  1206  		addAliveUnit{"private", "4"},
  1207  
  1208  		relateServices{"project", "mysql"},
  1209  		relateServices{"project", "varnish"},
  1210  		relateServices{"private", "mysql"},
  1211  
  1212  		expect{
  1213  			"multiples services with relations between some of them",
  1214  			M{
  1215  				"environment": "dummyenv",
  1216  				"machines": M{
  1217  					"0": machine0,
  1218  					"1": machine1,
  1219  					"2": machine2,
  1220  					"3": machine3,
  1221  					"4": machine4,
  1222  				},
  1223  				"services": M{
  1224  					"project": M{
  1225  						"charm":   "cs:quantal/wordpress-3",
  1226  						"exposed": true,
  1227  						"service-status": M{
  1228  							"current": "active",
  1229  							"since":   "01 Apr 15 01:23+10:00",
  1230  						},
  1231  						"units": M{
  1232  							"project/0": M{
  1233  								"machine":     "1",
  1234  								"agent-state": "started",
  1235  								"workload-status": M{
  1236  									"current": "active",
  1237  									"since":   "01 Apr 15 01:23+10:00",
  1238  								},
  1239  								"agent-status": M{
  1240  									"current": "idle",
  1241  									"since":   "01 Apr 15 01:23+10:00",
  1242  								},
  1243  								"public-address": "dummyenv-1.dns",
  1244  							},
  1245  						},
  1246  						"relations": M{
  1247  							"db":    L{"mysql"},
  1248  							"cache": L{"varnish"},
  1249  						},
  1250  					},
  1251  					"mysql": M{
  1252  						"charm":   "cs:quantal/mysql-1",
  1253  						"exposed": true,
  1254  						"service-status": M{
  1255  							"current": "active",
  1256  							"since":   "01 Apr 15 01:23+10:00",
  1257  						},
  1258  						"units": M{
  1259  							"mysql/0": M{
  1260  								"machine":     "2",
  1261  								"agent-state": "started",
  1262  								"workload-status": M{
  1263  									"current": "active",
  1264  									"since":   "01 Apr 15 01:23+10:00",
  1265  								},
  1266  								"agent-status": M{
  1267  									"current": "idle",
  1268  									"since":   "01 Apr 15 01:23+10:00",
  1269  								},
  1270  								"public-address": "dummyenv-2.dns",
  1271  							},
  1272  						},
  1273  						"relations": M{
  1274  							"server": L{"private", "project"},
  1275  						},
  1276  					},
  1277  					"varnish": M{
  1278  						"charm":   "cs:quantal/varnish-1",
  1279  						"exposed": true,
  1280  						"service-status": M{
  1281  							"current": "unknown",
  1282  							"message": "Waiting for agent initialization to finish",
  1283  							"since":   "01 Apr 15 01:23+10:00",
  1284  						},
  1285  						"units": M{
  1286  							"varnish/0": M{
  1287  								"machine":     "3",
  1288  								"agent-state": "pending",
  1289  								"workload-status": M{
  1290  									"current": "unknown",
  1291  									"message": "Waiting for agent initialization to finish",
  1292  									"since":   "01 Apr 15 01:23+10:00",
  1293  								},
  1294  								"agent-status": M{
  1295  									"current": "allocating",
  1296  									"since":   "01 Apr 15 01:23+10:00",
  1297  								},
  1298  								"public-address": "dummyenv-3.dns",
  1299  							},
  1300  						},
  1301  						"relations": M{
  1302  							"webcache": L{"project"},
  1303  						},
  1304  					},
  1305  					"private": M{
  1306  						"charm":   "cs:quantal/wordpress-3",
  1307  						"exposed": true,
  1308  						"service-status": M{
  1309  							"current": "unknown",
  1310  							"message": "Waiting for agent initialization to finish",
  1311  							"since":   "01 Apr 15 01:23+10:00",
  1312  						},
  1313  						"units": M{
  1314  							"private/0": M{
  1315  								"machine":     "4",
  1316  								"agent-state": "pending",
  1317  								"workload-status": M{
  1318  									"current": "unknown",
  1319  									"message": "Waiting for agent initialization to finish",
  1320  									"since":   "01 Apr 15 01:23+10:00",
  1321  								},
  1322  								"agent-status": M{
  1323  									"current": "allocating",
  1324  									"since":   "01 Apr 15 01:23+10:00",
  1325  								},
  1326  								"public-address": "dummyenv-4.dns",
  1327  							},
  1328  						},
  1329  						"relations": M{
  1330  							"db": L{"mysql"},
  1331  						},
  1332  					},
  1333  				},
  1334  			},
  1335  		},
  1336  	), test(
  1337  		"simple peer scenario",
  1338  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1339  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  1340  		startAliveMachine{"0"},
  1341  		setMachineStatus{"0", state.StatusStarted, ""},
  1342  		addCharm{"riak"},
  1343  		addCharm{"wordpress"},
  1344  
  1345  		addService{name: "riak", charm: "riak"},
  1346  		setServiceExposed{"riak", true},
  1347  		addMachine{machineId: "1", job: state.JobHostUnits},
  1348  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  1349  		startAliveMachine{"1"},
  1350  		setMachineStatus{"1", state.StatusStarted, ""},
  1351  		addAliveUnit{"riak", "1"},
  1352  		setAgentStatus{"riak/0", state.StatusIdle, "", nil},
  1353  		setUnitStatus{"riak/0", state.StatusActive, "", nil},
  1354  		addMachine{machineId: "2", job: state.JobHostUnits},
  1355  		setAddresses{"2", network.NewAddresses("dummyenv-2.dns")},
  1356  		startAliveMachine{"2"},
  1357  		setMachineStatus{"2", state.StatusStarted, ""},
  1358  		addAliveUnit{"riak", "2"},
  1359  		setAgentStatus{"riak/1", state.StatusIdle, "", nil},
  1360  		setUnitStatus{"riak/1", state.StatusActive, "", nil},
  1361  		addMachine{machineId: "3", job: state.JobHostUnits},
  1362  		setAddresses{"3", network.NewAddresses("dummyenv-3.dns")},
  1363  		startAliveMachine{"3"},
  1364  		setMachineStatus{"3", state.StatusStarted, ""},
  1365  		addAliveUnit{"riak", "3"},
  1366  		setAgentStatus{"riak/2", state.StatusIdle, "", nil},
  1367  		setUnitStatus{"riak/2", state.StatusActive, "", nil},
  1368  
  1369  		expect{
  1370  			"multiples related peer units",
  1371  			M{
  1372  				"environment": "dummyenv",
  1373  				"machines": M{
  1374  					"0": machine0,
  1375  					"1": machine1,
  1376  					"2": machine2,
  1377  					"3": machine3,
  1378  				},
  1379  				"services": M{
  1380  					"riak": M{
  1381  						"charm":   "cs:quantal/riak-7",
  1382  						"exposed": true,
  1383  						"service-status": M{
  1384  							"current": "active",
  1385  							"since":   "01 Apr 15 01:23+10:00",
  1386  						},
  1387  						"units": M{
  1388  							"riak/0": M{
  1389  								"machine":     "1",
  1390  								"agent-state": "started",
  1391  								"workload-status": M{
  1392  									"current": "active",
  1393  									"since":   "01 Apr 15 01:23+10:00",
  1394  								},
  1395  								"agent-status": M{
  1396  									"current": "idle",
  1397  									"since":   "01 Apr 15 01:23+10:00",
  1398  								},
  1399  								"public-address": "dummyenv-1.dns",
  1400  							},
  1401  							"riak/1": M{
  1402  								"machine":     "2",
  1403  								"agent-state": "started",
  1404  								"workload-status": M{
  1405  									"current": "active",
  1406  									"since":   "01 Apr 15 01:23+10:00",
  1407  								},
  1408  								"agent-status": M{
  1409  									"current": "idle",
  1410  									"since":   "01 Apr 15 01:23+10:00",
  1411  								},
  1412  								"public-address": "dummyenv-2.dns",
  1413  							},
  1414  							"riak/2": M{
  1415  								"machine":     "3",
  1416  								"agent-state": "started",
  1417  								"workload-status": M{
  1418  									"current": "active",
  1419  									"since":   "01 Apr 15 01:23+10:00",
  1420  								},
  1421  								"agent-status": M{
  1422  									"current": "idle",
  1423  									"since":   "01 Apr 15 01:23+10:00",
  1424  								},
  1425  								"public-address": "dummyenv-3.dns",
  1426  							},
  1427  						},
  1428  						"relations": M{
  1429  							"ring": L{"riak"},
  1430  						},
  1431  					},
  1432  				},
  1433  			},
  1434  		},
  1435  	),
  1436  
  1437  	// Subordinate tests
  1438  	test(
  1439  		"one service with one subordinate service",
  1440  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1441  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  1442  		startAliveMachine{"0"},
  1443  		setMachineStatus{"0", state.StatusStarted, ""},
  1444  		addCharm{"wordpress"},
  1445  		addCharm{"mysql"},
  1446  		addCharm{"logging"},
  1447  
  1448  		addService{name: "wordpress", charm: "wordpress"},
  1449  		setServiceExposed{"wordpress", true},
  1450  		addMachine{machineId: "1", job: state.JobHostUnits},
  1451  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  1452  		startAliveMachine{"1"},
  1453  		setMachineStatus{"1", state.StatusStarted, ""},
  1454  		addAliveUnit{"wordpress", "1"},
  1455  		setAgentStatus{"wordpress/0", state.StatusIdle, "", nil},
  1456  		setUnitStatus{"wordpress/0", state.StatusActive, "", nil},
  1457  
  1458  		addService{name: "mysql", charm: "mysql"},
  1459  		setServiceExposed{"mysql", true},
  1460  		addMachine{machineId: "2", job: state.JobHostUnits},
  1461  		setAddresses{"2", network.NewAddresses("dummyenv-2.dns")},
  1462  		startAliveMachine{"2"},
  1463  		setMachineStatus{"2", state.StatusStarted, ""},
  1464  		addAliveUnit{"mysql", "2"},
  1465  		setAgentStatus{"mysql/0", state.StatusIdle, "", nil},
  1466  		setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  1467  
  1468  		addService{name: "logging", charm: "logging"},
  1469  		setServiceExposed{"logging", true},
  1470  
  1471  		relateServices{"wordpress", "mysql"},
  1472  		relateServices{"wordpress", "logging"},
  1473  		relateServices{"mysql", "logging"},
  1474  
  1475  		addSubordinate{"wordpress/0", "logging"},
  1476  		addSubordinate{"mysql/0", "logging"},
  1477  
  1478  		setUnitsAlive{"logging"},
  1479  		setAgentStatus{"logging/0", state.StatusIdle, "", nil},
  1480  		setUnitStatus{"logging/0", state.StatusActive, "", nil},
  1481  		setAgentStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil},
  1482  
  1483  		expect{
  1484  			"multiples related peer units",
  1485  			M{
  1486  				"environment": "dummyenv",
  1487  				"machines": M{
  1488  					"0": machine0,
  1489  					"1": machine1,
  1490  					"2": machine2,
  1491  				},
  1492  				"services": M{
  1493  					"wordpress": M{
  1494  						"charm":   "cs:quantal/wordpress-3",
  1495  						"exposed": true,
  1496  						"service-status": M{
  1497  							"current": "active",
  1498  							"since":   "01 Apr 15 01:23+10:00",
  1499  						},
  1500  						"units": M{
  1501  							"wordpress/0": M{
  1502  								"machine":     "1",
  1503  								"agent-state": "started",
  1504  								"workload-status": M{
  1505  									"current": "active",
  1506  									"since":   "01 Apr 15 01:23+10:00",
  1507  								},
  1508  								"agent-status": M{
  1509  									"current": "idle",
  1510  									"since":   "01 Apr 15 01:23+10:00",
  1511  								},
  1512  								"subordinates": M{
  1513  									"logging/0": M{
  1514  										"agent-state": "started",
  1515  										"workload-status": M{
  1516  											"current": "active",
  1517  											"since":   "01 Apr 15 01:23+10:00",
  1518  										},
  1519  										"agent-status": M{
  1520  											"current": "idle",
  1521  											"since":   "01 Apr 15 01:23+10:00",
  1522  										},
  1523  										"public-address": "dummyenv-1.dns",
  1524  									},
  1525  								},
  1526  								"public-address": "dummyenv-1.dns",
  1527  							},
  1528  						},
  1529  						"relations": M{
  1530  							"db":          L{"mysql"},
  1531  							"logging-dir": L{"logging"},
  1532  						},
  1533  					},
  1534  					"mysql": M{
  1535  						"charm":   "cs:quantal/mysql-1",
  1536  						"exposed": true,
  1537  						"service-status": M{
  1538  							"current": "active",
  1539  							"since":   "01 Apr 15 01:23+10:00",
  1540  						},
  1541  						"units": M{
  1542  							"mysql/0": M{
  1543  								"machine":     "2",
  1544  								"agent-state": "started",
  1545  								"workload-status": M{
  1546  									"current": "active",
  1547  									"since":   "01 Apr 15 01:23+10:00",
  1548  								},
  1549  								"agent-status": M{
  1550  									"current": "idle",
  1551  									"since":   "01 Apr 15 01:23+10:00",
  1552  								},
  1553  								"subordinates": M{
  1554  									"logging/1": M{
  1555  										"agent-state":      "error",
  1556  										"agent-state-info": "somehow lost in all those logs",
  1557  										"workload-status": M{
  1558  											"current": "error",
  1559  											"message": "somehow lost in all those logs",
  1560  											"since":   "01 Apr 15 01:23+10:00",
  1561  										},
  1562  										"agent-status": M{
  1563  											"current": "idle",
  1564  											"since":   "01 Apr 15 01:23+10:00",
  1565  										},
  1566  										"public-address": "dummyenv-2.dns",
  1567  									},
  1568  								},
  1569  								"public-address": "dummyenv-2.dns",
  1570  							},
  1571  						},
  1572  						"relations": M{
  1573  							"server":    L{"wordpress"},
  1574  							"juju-info": L{"logging"},
  1575  						},
  1576  					},
  1577  					"logging": M{
  1578  						"charm":          "cs:quantal/logging-1",
  1579  						"exposed":        true,
  1580  						"service-status": M{},
  1581  						"relations": M{
  1582  							"logging-directory": L{"wordpress"},
  1583  							"info":              L{"mysql"},
  1584  						},
  1585  						"subordinate-to": L{"mysql", "wordpress"},
  1586  					},
  1587  				},
  1588  			},
  1589  		},
  1590  
  1591  		// scoped on 'logging'
  1592  		scopedExpect{
  1593  			"subordinates scoped on logging",
  1594  			[]string{"logging"},
  1595  			M{
  1596  				"environment": "dummyenv",
  1597  				"machines": M{
  1598  					"1": machine1,
  1599  					"2": machine2,
  1600  				},
  1601  				"services": M{
  1602  					"wordpress": M{
  1603  						"charm":   "cs:quantal/wordpress-3",
  1604  						"exposed": true,
  1605  						"service-status": M{
  1606  							"current": "active",
  1607  							"since":   "01 Apr 15 01:23+10:00",
  1608  						},
  1609  						"units": M{
  1610  							"wordpress/0": M{
  1611  								"machine":     "1",
  1612  								"agent-state": "started",
  1613  								"workload-status": M{
  1614  									"current": "active",
  1615  									"since":   "01 Apr 15 01:23+10:00",
  1616  								},
  1617  								"agent-status": M{
  1618  									"current": "idle",
  1619  									"since":   "01 Apr 15 01:23+10:00",
  1620  								},
  1621  								"subordinates": M{
  1622  									"logging/0": M{
  1623  										"agent-state": "started",
  1624  										"workload-status": M{
  1625  											"current": "active",
  1626  											"since":   "01 Apr 15 01:23+10:00",
  1627  										},
  1628  										"agent-status": M{
  1629  											"current": "idle",
  1630  											"since":   "01 Apr 15 01:23+10:00",
  1631  										},
  1632  										"public-address": "dummyenv-1.dns",
  1633  									},
  1634  								},
  1635  								"public-address": "dummyenv-1.dns",
  1636  							},
  1637  						},
  1638  						"relations": M{
  1639  							"db":          L{"mysql"},
  1640  							"logging-dir": L{"logging"},
  1641  						},
  1642  					},
  1643  					"mysql": M{
  1644  						"charm":   "cs:quantal/mysql-1",
  1645  						"exposed": true,
  1646  						"service-status": M{
  1647  							"current": "active",
  1648  							"since":   "01 Apr 15 01:23+10:00",
  1649  						},
  1650  						"units": M{
  1651  							"mysql/0": M{
  1652  								"machine":     "2",
  1653  								"agent-state": "started",
  1654  								"workload-status": M{
  1655  									"current": "active",
  1656  									"since":   "01 Apr 15 01:23+10:00",
  1657  								},
  1658  								"agent-status": M{
  1659  									"current": "idle",
  1660  									"since":   "01 Apr 15 01:23+10:00",
  1661  								},
  1662  								"subordinates": M{
  1663  									"logging/1": M{
  1664  										"agent-state": "error",
  1665  										"workload-status": M{
  1666  											"current": "error",
  1667  											"message": "somehow lost in all those logs",
  1668  											"since":   "01 Apr 15 01:23+10:00",
  1669  										},
  1670  										"agent-status": M{
  1671  											"current": "idle",
  1672  											"since":   "01 Apr 15 01:23+10:00",
  1673  										},
  1674  										"agent-state-info": "somehow lost in all those logs",
  1675  										"public-address":   "dummyenv-2.dns",
  1676  									},
  1677  								},
  1678  								"public-address": "dummyenv-2.dns",
  1679  							},
  1680  						},
  1681  						"relations": M{
  1682  							"server":    L{"wordpress"},
  1683  							"juju-info": L{"logging"},
  1684  						},
  1685  					},
  1686  					"logging": M{
  1687  						"charm":          "cs:quantal/logging-1",
  1688  						"exposed":        true,
  1689  						"service-status": M{},
  1690  						"relations": M{
  1691  							"logging-directory": L{"wordpress"},
  1692  							"info":              L{"mysql"},
  1693  						},
  1694  						"subordinate-to": L{"mysql", "wordpress"},
  1695  					},
  1696  				},
  1697  			},
  1698  		},
  1699  
  1700  		// scoped on wordpress/0
  1701  		scopedExpect{
  1702  			"subordinates scoped on logging",
  1703  			[]string{"wordpress/0"},
  1704  			M{
  1705  				"environment": "dummyenv",
  1706  				"machines": M{
  1707  					"1": machine1,
  1708  				},
  1709  				"services": M{
  1710  					"wordpress": M{
  1711  						"charm":   "cs:quantal/wordpress-3",
  1712  						"exposed": true,
  1713  						"service-status": M{
  1714  							"current": "active",
  1715  							"since":   "01 Apr 15 01:23+10:00",
  1716  						},
  1717  						"units": M{
  1718  							"wordpress/0": M{
  1719  								"machine":     "1",
  1720  								"agent-state": "started",
  1721  								"workload-status": M{
  1722  									"current": "active",
  1723  									"since":   "01 Apr 15 01:23+10:00",
  1724  								},
  1725  								"agent-status": M{
  1726  									"current": "idle",
  1727  									"since":   "01 Apr 15 01:23+10:00",
  1728  								},
  1729  								"subordinates": M{
  1730  									"logging/0": M{
  1731  										"agent-state": "started",
  1732  										"workload-status": M{
  1733  											"current": "active",
  1734  											"since":   "01 Apr 15 01:23+10:00",
  1735  										},
  1736  										"agent-status": M{
  1737  											"current": "idle",
  1738  											"since":   "01 Apr 15 01:23+10:00",
  1739  										},
  1740  										"public-address": "dummyenv-1.dns",
  1741  									},
  1742  								},
  1743  								"public-address": "dummyenv-1.dns",
  1744  							},
  1745  						},
  1746  						"relations": M{
  1747  							"db":          L{"mysql"},
  1748  							"logging-dir": L{"logging"},
  1749  						},
  1750  					},
  1751  					"logging": M{
  1752  						"charm":          "cs:quantal/logging-1",
  1753  						"exposed":        true,
  1754  						"service-status": M{},
  1755  						"relations": M{
  1756  							"logging-directory": L{"wordpress"},
  1757  							"info":              L{"mysql"},
  1758  						},
  1759  						"subordinate-to": L{"mysql", "wordpress"},
  1760  					},
  1761  				},
  1762  			},
  1763  		},
  1764  	),
  1765  	test(
  1766  		"machines with containers",
  1767  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1768  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  1769  		startAliveMachine{"0"},
  1770  		setMachineStatus{"0", state.StatusStarted, ""},
  1771  		addCharm{"mysql"},
  1772  		addService{name: "mysql", charm: "mysql"},
  1773  		setServiceExposed{"mysql", true},
  1774  
  1775  		addMachine{machineId: "1", job: state.JobHostUnits},
  1776  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  1777  		startAliveMachine{"1"},
  1778  		setMachineStatus{"1", state.StatusStarted, ""},
  1779  		addAliveUnit{"mysql", "1"},
  1780  		setAgentStatus{"mysql/0", state.StatusIdle, "", nil},
  1781  		setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  1782  
  1783  		// A container on machine 1.
  1784  		addContainer{"1", "1/lxc/0", state.JobHostUnits},
  1785  		setAddresses{"1/lxc/0", network.NewAddresses("dummyenv-2.dns")},
  1786  		startAliveMachine{"1/lxc/0"},
  1787  		setMachineStatus{"1/lxc/0", state.StatusStarted, ""},
  1788  		addAliveUnit{"mysql", "1/lxc/0"},
  1789  		setAgentStatus{"mysql/1", state.StatusIdle, "", nil},
  1790  		setUnitStatus{"mysql/1", state.StatusActive, "", nil},
  1791  		addContainer{"1", "1/lxc/1", state.JobHostUnits},
  1792  
  1793  		// A nested container.
  1794  		addContainer{"1/lxc/0", "1/lxc/0/lxc/0", state.JobHostUnits},
  1795  		setAddresses{"1/lxc/0/lxc/0", network.NewAddresses("dummyenv-3.dns")},
  1796  		startAliveMachine{"1/lxc/0/lxc/0"},
  1797  		setMachineStatus{"1/lxc/0/lxc/0", state.StatusStarted, ""},
  1798  
  1799  		expect{
  1800  			"machines with nested containers",
  1801  			M{
  1802  				"environment": "dummyenv",
  1803  				"machines": M{
  1804  					"0": machine0,
  1805  					"1": machine1WithContainers,
  1806  				},
  1807  				"services": M{
  1808  					"mysql": M{
  1809  						"charm":   "cs:quantal/mysql-1",
  1810  						"exposed": true,
  1811  						"service-status": M{
  1812  							"current": "active",
  1813  							"since":   "01 Apr 15 01:23+10:00",
  1814  						},
  1815  						"units": M{
  1816  							"mysql/0": M{
  1817  								"machine":     "1",
  1818  								"agent-state": "started",
  1819  								"workload-status": M{
  1820  									"current": "active",
  1821  									"since":   "01 Apr 15 01:23+10:00",
  1822  								},
  1823  								"agent-status": M{
  1824  									"current": "idle",
  1825  									"since":   "01 Apr 15 01:23+10:00",
  1826  								},
  1827  								"public-address": "dummyenv-1.dns",
  1828  							},
  1829  							"mysql/1": M{
  1830  								"machine":     "1/lxc/0",
  1831  								"agent-state": "started",
  1832  								"workload-status": M{
  1833  									"current": "active",
  1834  									"since":   "01 Apr 15 01:23+10:00",
  1835  								},
  1836  								"agent-status": M{
  1837  									"current": "idle",
  1838  									"since":   "01 Apr 15 01:23+10:00",
  1839  								},
  1840  								"public-address": "dummyenv-2.dns",
  1841  							},
  1842  						},
  1843  					},
  1844  				},
  1845  			},
  1846  		},
  1847  
  1848  		// once again, with a scope on mysql/1
  1849  		scopedExpect{
  1850  			"machines with nested containers",
  1851  			[]string{"mysql/1"},
  1852  			M{
  1853  				"environment": "dummyenv",
  1854  				"machines": M{
  1855  					"1": M{
  1856  						"agent-state": "started",
  1857  						"containers": M{
  1858  							"1/lxc/0": M{
  1859  								"agent-state": "started",
  1860  								"dns-name":    "dummyenv-2.dns",
  1861  								"instance-id": "dummyenv-2",
  1862  								"series":      "quantal",
  1863  							},
  1864  						},
  1865  						"dns-name":    "dummyenv-1.dns",
  1866  						"instance-id": "dummyenv-1",
  1867  						"series":      "quantal",
  1868  						"hardware":    "arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M",
  1869  					},
  1870  				},
  1871  				"services": M{
  1872  					"mysql": M{
  1873  						"charm":   "cs:quantal/mysql-1",
  1874  						"exposed": true,
  1875  						"service-status": M{
  1876  							"current": "active",
  1877  							"since":   "01 Apr 15 01:23+10:00",
  1878  						},
  1879  						"units": M{
  1880  							"mysql/1": M{
  1881  								"machine":     "1/lxc/0",
  1882  								"agent-state": "started",
  1883  								"workload-status": M{
  1884  									"current": "active",
  1885  									"since":   "01 Apr 15 01:23+10:00",
  1886  								},
  1887  								"agent-status": M{
  1888  									"current": "idle",
  1889  									"since":   "01 Apr 15 01:23+10:00",
  1890  								},
  1891  								"public-address": "dummyenv-2.dns",
  1892  							},
  1893  						},
  1894  					},
  1895  				},
  1896  			},
  1897  		},
  1898  	), test(
  1899  		"service with out of date charm",
  1900  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1901  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  1902  		startAliveMachine{"0"},
  1903  		setMachineStatus{"0", state.StatusStarted, ""},
  1904  		addMachine{machineId: "1", job: state.JobHostUnits},
  1905  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  1906  		startAliveMachine{"1"},
  1907  		setMachineStatus{"1", state.StatusStarted, ""},
  1908  		addCharm{"mysql"},
  1909  		addService{name: "mysql", charm: "mysql"},
  1910  		setServiceExposed{"mysql", true},
  1911  		addCharmPlaceholder{"mysql", 23},
  1912  		addAliveUnit{"mysql", "1"},
  1913  
  1914  		expect{
  1915  			"services and units with correct charm status",
  1916  			M{
  1917  				"environment": "dummyenv",
  1918  				"machines": M{
  1919  					"0": machine0,
  1920  					"1": machine1,
  1921  				},
  1922  				"services": M{
  1923  					"mysql": M{
  1924  						"charm":          "cs:quantal/mysql-1",
  1925  						"can-upgrade-to": "cs:quantal/mysql-23",
  1926  						"exposed":        true,
  1927  						"service-status": M{
  1928  							"current": "unknown",
  1929  							"message": "Waiting for agent initialization to finish",
  1930  							"since":   "01 Apr 15 01:23+10:00",
  1931  						},
  1932  						"units": M{
  1933  							"mysql/0": M{
  1934  								"machine":     "1",
  1935  								"agent-state": "pending",
  1936  								"workload-status": M{
  1937  									"current": "unknown",
  1938  									"message": "Waiting for agent initialization to finish",
  1939  									"since":   "01 Apr 15 01:23+10:00",
  1940  								},
  1941  								"agent-status": M{
  1942  									"current": "allocating",
  1943  									"since":   "01 Apr 15 01:23+10:00",
  1944  								},
  1945  								"public-address": "dummyenv-1.dns",
  1946  							},
  1947  						},
  1948  					},
  1949  				},
  1950  			},
  1951  		},
  1952  	), test(
  1953  		"unit with out of date charm",
  1954  		addMachine{machineId: "0", job: state.JobManageEnviron},
  1955  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  1956  		startAliveMachine{"0"},
  1957  		setMachineStatus{"0", state.StatusStarted, ""},
  1958  		addMachine{machineId: "1", job: state.JobHostUnits},
  1959  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  1960  		startAliveMachine{"1"},
  1961  		setMachineStatus{"1", state.StatusStarted, ""},
  1962  		addCharm{"mysql"},
  1963  		addService{name: "mysql", charm: "mysql"},
  1964  		setServiceExposed{"mysql", true},
  1965  		addAliveUnit{"mysql", "1"},
  1966  		setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  1967  		addCharmWithRevision{addCharm{"mysql"}, "local", 1},
  1968  		setServiceCharm{"mysql", "local:quantal/mysql-1"},
  1969  
  1970  		expect{
  1971  			"services and units with correct charm status",
  1972  			M{
  1973  				"environment": "dummyenv",
  1974  				"machines": M{
  1975  					"0": machine0,
  1976  					"1": machine1,
  1977  				},
  1978  				"services": M{
  1979  					"mysql": M{
  1980  						"charm":   "local:quantal/mysql-1",
  1981  						"exposed": true,
  1982  						"service-status": M{
  1983  							"current": "active",
  1984  							"since":   "01 Apr 15 01:23+10:00",
  1985  						},
  1986  						"units": M{
  1987  							"mysql/0": M{
  1988  								"machine":     "1",
  1989  								"agent-state": "started",
  1990  								"workload-status": M{
  1991  									"current": "active",
  1992  									"since":   "01 Apr 15 01:23+10:00",
  1993  								},
  1994  								"agent-status": M{
  1995  									"current": "idle",
  1996  									"since":   "01 Apr 15 01:23+10:00",
  1997  								},
  1998  								"upgrading-from": "cs:quantal/mysql-1",
  1999  								"public-address": "dummyenv-1.dns",
  2000  							},
  2001  						},
  2002  					},
  2003  				},
  2004  			},
  2005  		},
  2006  	), test(
  2007  		"service and unit with out of date charms",
  2008  		addMachine{machineId: "0", job: state.JobManageEnviron},
  2009  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  2010  		startAliveMachine{"0"},
  2011  		setMachineStatus{"0", state.StatusStarted, ""},
  2012  		addMachine{machineId: "1", job: state.JobHostUnits},
  2013  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  2014  		startAliveMachine{"1"},
  2015  		setMachineStatus{"1", state.StatusStarted, ""},
  2016  		addCharm{"mysql"},
  2017  		addService{name: "mysql", charm: "mysql"},
  2018  		setServiceExposed{"mysql", true},
  2019  		addAliveUnit{"mysql", "1"},
  2020  		setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  2021  		addCharmWithRevision{addCharm{"mysql"}, "cs", 2},
  2022  		setServiceCharm{"mysql", "cs:quantal/mysql-2"},
  2023  		addCharmPlaceholder{"mysql", 23},
  2024  
  2025  		expect{
  2026  			"services and units with correct charm status",
  2027  			M{
  2028  				"environment": "dummyenv",
  2029  				"machines": M{
  2030  					"0": machine0,
  2031  					"1": machine1,
  2032  				},
  2033  				"services": M{
  2034  					"mysql": M{
  2035  						"charm":          "cs:quantal/mysql-2",
  2036  						"can-upgrade-to": "cs:quantal/mysql-23",
  2037  						"exposed":        true,
  2038  						"service-status": M{
  2039  							"current": "active",
  2040  							"since":   "01 Apr 15 01:23+10:00",
  2041  						},
  2042  						"units": M{
  2043  							"mysql/0": M{
  2044  								"machine":     "1",
  2045  								"agent-state": "started",
  2046  								"workload-status": M{
  2047  									"current": "active",
  2048  									"since":   "01 Apr 15 01:23+10:00",
  2049  								},
  2050  								"agent-status": M{
  2051  									"current": "idle",
  2052  									"since":   "01 Apr 15 01:23+10:00",
  2053  								},
  2054  								"upgrading-from": "cs:quantal/mysql-1",
  2055  								"public-address": "dummyenv-1.dns",
  2056  							},
  2057  						},
  2058  					},
  2059  				},
  2060  			},
  2061  		},
  2062  	), test(
  2063  		"service with local charm not shown as out of date",
  2064  		addMachine{machineId: "0", job: state.JobManageEnviron},
  2065  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  2066  		startAliveMachine{"0"},
  2067  		setMachineStatus{"0", state.StatusStarted, ""},
  2068  		addMachine{machineId: "1", job: state.JobHostUnits},
  2069  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  2070  		startAliveMachine{"1"},
  2071  		setMachineStatus{"1", state.StatusStarted, ""},
  2072  		addCharm{"mysql"},
  2073  		addService{name: "mysql", charm: "mysql"},
  2074  		setServiceExposed{"mysql", true},
  2075  		addAliveUnit{"mysql", "1"},
  2076  		setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  2077  		addCharmWithRevision{addCharm{"mysql"}, "local", 1},
  2078  		setServiceCharm{"mysql", "local:quantal/mysql-1"},
  2079  		addCharmPlaceholder{"mysql", 23},
  2080  
  2081  		expect{
  2082  			"services and units with correct charm status",
  2083  			M{
  2084  				"environment": "dummyenv",
  2085  				"machines": M{
  2086  					"0": machine0,
  2087  					"1": machine1,
  2088  				},
  2089  				"services": M{
  2090  					"mysql": M{
  2091  						"charm":   "local:quantal/mysql-1",
  2092  						"exposed": true,
  2093  						"service-status": M{
  2094  							"current": "active",
  2095  							"since":   "01 Apr 15 01:23+10:00",
  2096  						},
  2097  						"units": M{
  2098  							"mysql/0": M{
  2099  								"machine":     "1",
  2100  								"agent-state": "started",
  2101  								"workload-status": M{
  2102  									"current": "active",
  2103  									"since":   "01 Apr 15 01:23+10:00",
  2104  								},
  2105  								"agent-status": M{
  2106  									"current": "idle",
  2107  									"since":   "01 Apr 15 01:23+10:00",
  2108  								},
  2109  								"upgrading-from": "cs:quantal/mysql-1",
  2110  								"public-address": "dummyenv-1.dns",
  2111  							},
  2112  						},
  2113  					},
  2114  				},
  2115  			},
  2116  		},
  2117  	), test(
  2118  		"deploy two services; set meter statuses on one",
  2119  		addMachine{machineId: "0", job: state.JobManageEnviron},
  2120  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  2121  		startAliveMachine{"0"},
  2122  		setMachineStatus{"0", state.StatusStarted, ""},
  2123  
  2124  		addMachine{machineId: "1", job: state.JobHostUnits},
  2125  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  2126  		startAliveMachine{"1"},
  2127  		setMachineStatus{"1", state.StatusStarted, ""},
  2128  
  2129  		addMachine{machineId: "2", job: state.JobHostUnits},
  2130  		setAddresses{"2", network.NewAddresses("dummyenv-2.dns")},
  2131  		startAliveMachine{"2"},
  2132  		setMachineStatus{"2", state.StatusStarted, ""},
  2133  
  2134  		addMachine{machineId: "3", job: state.JobHostUnits},
  2135  		setAddresses{"3", network.NewAddresses("dummyenv-3.dns")},
  2136  		startAliveMachine{"3"},
  2137  		setMachineStatus{"3", state.StatusStarted, ""},
  2138  
  2139  		addMachine{machineId: "4", job: state.JobHostUnits},
  2140  		setAddresses{"4", network.NewAddresses("dummyenv-4.dns")},
  2141  		startAliveMachine{"4"},
  2142  		setMachineStatus{"4", state.StatusStarted, ""},
  2143  
  2144  		addCharm{"mysql"},
  2145  		addService{name: "mysql", charm: "mysql"},
  2146  		setServiceExposed{"mysql", true},
  2147  
  2148  		addService{name: "servicewithmeterstatus", charm: "mysql"},
  2149  
  2150  		addAliveUnit{"mysql", "1"},
  2151  		addAliveUnit{"servicewithmeterstatus", "2"},
  2152  		addAliveUnit{"servicewithmeterstatus", "3"},
  2153  		addAliveUnit{"servicewithmeterstatus", "4"},
  2154  
  2155  		setServiceExposed{"mysql", true},
  2156  
  2157  		setAgentStatus{"mysql/0", state.StatusIdle, "", nil},
  2158  		setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  2159  		setAgentStatus{"servicewithmeterstatus/0", state.StatusIdle, "", nil},
  2160  		setUnitStatus{"servicewithmeterstatus/0", state.StatusActive, "", nil},
  2161  		setAgentStatus{"servicewithmeterstatus/1", state.StatusIdle, "", nil},
  2162  		setUnitStatus{"servicewithmeterstatus/1", state.StatusActive, "", nil},
  2163  		setAgentStatus{"servicewithmeterstatus/2", state.StatusIdle, "", nil},
  2164  		setUnitStatus{"servicewithmeterstatus/2", state.StatusActive, "", nil},
  2165  
  2166  		setUnitMeterStatus{"servicewithmeterstatus/1", "GREEN", "test green status"},
  2167  		setUnitMeterStatus{"servicewithmeterstatus/2", "RED", "test red status"},
  2168  
  2169  		expect{
  2170  			"simulate just the two services and a bootstrap node",
  2171  			M{
  2172  				"environment": "dummyenv",
  2173  				"machines": M{
  2174  					"0": machine0,
  2175  					"1": machine1,
  2176  					"2": machine2,
  2177  					"3": machine3,
  2178  					"4": machine4,
  2179  				},
  2180  				"services": M{
  2181  					"mysql": M{
  2182  						"charm":   "cs:quantal/mysql-1",
  2183  						"exposed": true,
  2184  						"service-status": M{
  2185  							"current": "active",
  2186  							"since":   "01 Apr 15 01:23+10:00",
  2187  						},
  2188  						"units": M{
  2189  							"mysql/0": M{
  2190  								"machine":     "1",
  2191  								"agent-state": "started",
  2192  								"workload-status": M{
  2193  									"current": "active",
  2194  									"since":   "01 Apr 15 01:23+10:00",
  2195  								},
  2196  								"agent-status": M{
  2197  									"current": "idle",
  2198  									"since":   "01 Apr 15 01:23+10:00",
  2199  								},
  2200  								"public-address": "dummyenv-1.dns",
  2201  							},
  2202  						},
  2203  					},
  2204  
  2205  					"servicewithmeterstatus": M{
  2206  						"charm":   "cs:quantal/mysql-1",
  2207  						"exposed": false,
  2208  						"service-status": M{
  2209  							"current": "active",
  2210  							"since":   "01 Apr 15 01:23+10:00",
  2211  						},
  2212  						"units": M{
  2213  							"servicewithmeterstatus/0": M{
  2214  								"machine":     "2",
  2215  								"agent-state": "started",
  2216  								"workload-status": M{
  2217  									"current": "active",
  2218  									"since":   "01 Apr 15 01:23+10:00",
  2219  								},
  2220  								"agent-status": M{
  2221  									"current": "idle",
  2222  									"since":   "01 Apr 15 01:23+10:00",
  2223  								},
  2224  								"public-address": "dummyenv-2.dns",
  2225  							},
  2226  							"servicewithmeterstatus/1": M{
  2227  								"machine":     "3",
  2228  								"agent-state": "started",
  2229  								"workload-status": M{
  2230  									"current": "active",
  2231  									"since":   "01 Apr 15 01:23+10:00",
  2232  								},
  2233  								"agent-status": M{
  2234  									"current": "idle",
  2235  									"since":   "01 Apr 15 01:23+10:00",
  2236  								},
  2237  								"meter-status": M{
  2238  									"color":   "green",
  2239  									"message": "test green status",
  2240  								},
  2241  								"public-address": "dummyenv-3.dns",
  2242  							},
  2243  							"servicewithmeterstatus/2": M{
  2244  								"machine":     "4",
  2245  								"agent-state": "started",
  2246  								"workload-status": M{
  2247  									"current": "active",
  2248  									"since":   "01 Apr 15 01:23+10:00",
  2249  								},
  2250  								"agent-status": M{
  2251  									"current": "idle",
  2252  									"since":   "01 Apr 15 01:23+10:00",
  2253  								},
  2254  								"meter-status": M{
  2255  									"color":   "red",
  2256  									"message": "test red status",
  2257  								},
  2258  								"public-address": "dummyenv-4.dns",
  2259  							},
  2260  						},
  2261  					},
  2262  				},
  2263  			},
  2264  		},
  2265  	),
  2266  }
  2267  
  2268  // TODO(dfc) test failing components by destructively mutating the state under the hood
  2269  
  2270  type addMachine struct {
  2271  	machineId string
  2272  	cons      constraints.Value
  2273  	job       state.MachineJob
  2274  }
  2275  
  2276  func (am addMachine) step(c *gc.C, ctx *context) {
  2277  	m, err := ctx.st.AddOneMachine(state.MachineTemplate{
  2278  		Series:      "quantal",
  2279  		Constraints: am.cons,
  2280  		Jobs:        []state.MachineJob{am.job},
  2281  	})
  2282  	c.Assert(err, jc.ErrorIsNil)
  2283  	c.Assert(m.Id(), gc.Equals, am.machineId)
  2284  }
  2285  
  2286  type addNetwork struct {
  2287  	name       string
  2288  	providerId network.Id
  2289  	cidr       string
  2290  	vlanTag    int
  2291  }
  2292  
  2293  func (an addNetwork) step(c *gc.C, ctx *context) {
  2294  	n, err := ctx.st.AddNetwork(state.NetworkInfo{
  2295  		Name:       an.name,
  2296  		ProviderId: an.providerId,
  2297  		CIDR:       an.cidr,
  2298  		VLANTag:    an.vlanTag,
  2299  	})
  2300  	c.Assert(err, jc.ErrorIsNil)
  2301  	c.Assert(n.Name(), gc.Equals, an.name)
  2302  }
  2303  
  2304  type addContainer struct {
  2305  	parentId  string
  2306  	machineId string
  2307  	job       state.MachineJob
  2308  }
  2309  
  2310  func (ac addContainer) step(c *gc.C, ctx *context) {
  2311  	template := state.MachineTemplate{
  2312  		Series: "quantal",
  2313  		Jobs:   []state.MachineJob{ac.job},
  2314  	}
  2315  	m, err := ctx.st.AddMachineInsideMachine(template, ac.parentId, instance.LXC)
  2316  	c.Assert(err, jc.ErrorIsNil)
  2317  	c.Assert(m.Id(), gc.Equals, ac.machineId)
  2318  }
  2319  
  2320  type startMachine struct {
  2321  	machineId string
  2322  }
  2323  
  2324  func (sm startMachine) step(c *gc.C, ctx *context) {
  2325  	m, err := ctx.st.Machine(sm.machineId)
  2326  	c.Assert(err, jc.ErrorIsNil)
  2327  	cons, err := m.Constraints()
  2328  	c.Assert(err, jc.ErrorIsNil)
  2329  	inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, m.Id(), cons)
  2330  	err = m.SetProvisioned(inst.Id(), "fake_nonce", hc)
  2331  	c.Assert(err, jc.ErrorIsNil)
  2332  }
  2333  
  2334  type startMissingMachine struct {
  2335  	machineId string
  2336  }
  2337  
  2338  func (sm startMissingMachine) step(c *gc.C, ctx *context) {
  2339  	m, err := ctx.st.Machine(sm.machineId)
  2340  	c.Assert(err, jc.ErrorIsNil)
  2341  	cons, err := m.Constraints()
  2342  	c.Assert(err, jc.ErrorIsNil)
  2343  	_, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, m.Id(), cons)
  2344  	err = m.SetProvisioned("i-missing", "fake_nonce", hc)
  2345  	c.Assert(err, jc.ErrorIsNil)
  2346  	err = m.SetInstanceStatus("missing")
  2347  	c.Assert(err, jc.ErrorIsNil)
  2348  }
  2349  
  2350  type startAliveMachine struct {
  2351  	machineId string
  2352  }
  2353  
  2354  func (sam startAliveMachine) step(c *gc.C, ctx *context) {
  2355  	m, err := ctx.st.Machine(sam.machineId)
  2356  	c.Assert(err, jc.ErrorIsNil)
  2357  	pinger := ctx.setAgentPresence(c, m)
  2358  	cons, err := m.Constraints()
  2359  	c.Assert(err, jc.ErrorIsNil)
  2360  	inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, m.Id(), cons)
  2361  	err = m.SetProvisioned(inst.Id(), "fake_nonce", hc)
  2362  	c.Assert(err, jc.ErrorIsNil)
  2363  	ctx.pingers[m.Id()] = pinger
  2364  }
  2365  
  2366  type setAddresses struct {
  2367  	machineId string
  2368  	addresses []network.Address
  2369  }
  2370  
  2371  func (sa setAddresses) step(c *gc.C, ctx *context) {
  2372  	m, err := ctx.st.Machine(sa.machineId)
  2373  	c.Assert(err, jc.ErrorIsNil)
  2374  	err = m.SetProviderAddresses(sa.addresses...)
  2375  	c.Assert(err, jc.ErrorIsNil)
  2376  }
  2377  
  2378  type setTools struct {
  2379  	machineId string
  2380  	version   version.Binary
  2381  }
  2382  
  2383  func (st setTools) step(c *gc.C, ctx *context) {
  2384  	m, err := ctx.st.Machine(st.machineId)
  2385  	c.Assert(err, jc.ErrorIsNil)
  2386  	err = m.SetAgentVersion(st.version)
  2387  	c.Assert(err, jc.ErrorIsNil)
  2388  }
  2389  
  2390  type setUnitTools struct {
  2391  	unitName string
  2392  	version  version.Binary
  2393  }
  2394  
  2395  func (st setUnitTools) step(c *gc.C, ctx *context) {
  2396  	m, err := ctx.st.Unit(st.unitName)
  2397  	c.Assert(err, jc.ErrorIsNil)
  2398  	err = m.SetAgentVersion(st.version)
  2399  	c.Assert(err, jc.ErrorIsNil)
  2400  }
  2401  
  2402  type addCharm struct {
  2403  	name string
  2404  }
  2405  
  2406  func (ac addCharm) addCharmStep(c *gc.C, ctx *context, scheme string, rev int) {
  2407  	ch := testcharms.Repo.CharmDir(ac.name)
  2408  	name := ch.Meta().Name
  2409  	curl := charm.MustParseURL(fmt.Sprintf("%s:quantal/%s-%d", scheme, name, rev))
  2410  	dummy, err := ctx.st.AddCharm(ch, curl, "dummy-path", fmt.Sprintf("%s-%d-sha256", name, rev))
  2411  	c.Assert(err, jc.ErrorIsNil)
  2412  	ctx.charms[ac.name] = dummy
  2413  }
  2414  
  2415  func (ac addCharm) step(c *gc.C, ctx *context) {
  2416  	ch := testcharms.Repo.CharmDir(ac.name)
  2417  	ac.addCharmStep(c, ctx, "cs", ch.Revision())
  2418  }
  2419  
  2420  type addCharmWithRevision struct {
  2421  	addCharm
  2422  	scheme string
  2423  	rev    int
  2424  }
  2425  
  2426  func (ac addCharmWithRevision) step(c *gc.C, ctx *context) {
  2427  	ac.addCharmStep(c, ctx, ac.scheme, ac.rev)
  2428  }
  2429  
  2430  type addService struct {
  2431  	name     string
  2432  	charm    string
  2433  	networks []string
  2434  	cons     constraints.Value
  2435  }
  2436  
  2437  func (as addService) step(c *gc.C, ctx *context) {
  2438  	ch, ok := ctx.charms[as.charm]
  2439  	c.Assert(ok, jc.IsTrue)
  2440  	svc, err := ctx.st.AddService(as.name, ctx.adminUserTag, ch, as.networks, nil)
  2441  	c.Assert(err, jc.ErrorIsNil)
  2442  	if svc.IsPrincipal() {
  2443  		err = svc.SetConstraints(as.cons)
  2444  		c.Assert(err, jc.ErrorIsNil)
  2445  	}
  2446  }
  2447  
  2448  type setServiceExposed struct {
  2449  	name    string
  2450  	exposed bool
  2451  }
  2452  
  2453  func (sse setServiceExposed) step(c *gc.C, ctx *context) {
  2454  	s, err := ctx.st.Service(sse.name)
  2455  	c.Assert(err, jc.ErrorIsNil)
  2456  	err = s.ClearExposed()
  2457  	c.Assert(err, jc.ErrorIsNil)
  2458  	if sse.exposed {
  2459  		err = s.SetExposed()
  2460  		c.Assert(err, jc.ErrorIsNil)
  2461  	}
  2462  }
  2463  
  2464  type setServiceCharm struct {
  2465  	name  string
  2466  	charm string
  2467  }
  2468  
  2469  func (ssc setServiceCharm) step(c *gc.C, ctx *context) {
  2470  	ch, err := ctx.st.Charm(charm.MustParseURL(ssc.charm))
  2471  	c.Assert(err, jc.ErrorIsNil)
  2472  	s, err := ctx.st.Service(ssc.name)
  2473  	c.Assert(err, jc.ErrorIsNil)
  2474  	err = s.SetCharm(ch, false)
  2475  	c.Assert(err, jc.ErrorIsNil)
  2476  }
  2477  
  2478  type addCharmPlaceholder struct {
  2479  	name string
  2480  	rev  int
  2481  }
  2482  
  2483  func (ac addCharmPlaceholder) step(c *gc.C, ctx *context) {
  2484  	ch := testcharms.Repo.CharmDir(ac.name)
  2485  	name := ch.Meta().Name
  2486  	curl := charm.MustParseURL(fmt.Sprintf("cs:quantal/%s-%d", name, ac.rev))
  2487  	err := ctx.st.AddStoreCharmPlaceholder(curl)
  2488  	c.Assert(err, jc.ErrorIsNil)
  2489  }
  2490  
  2491  type addUnit struct {
  2492  	serviceName string
  2493  	machineId   string
  2494  }
  2495  
  2496  func (au addUnit) step(c *gc.C, ctx *context) {
  2497  	s, err := ctx.st.Service(au.serviceName)
  2498  	c.Assert(err, jc.ErrorIsNil)
  2499  	u, err := s.AddUnit()
  2500  	c.Assert(err, jc.ErrorIsNil)
  2501  	m, err := ctx.st.Machine(au.machineId)
  2502  	c.Assert(err, jc.ErrorIsNil)
  2503  	err = u.AssignToMachine(m)
  2504  	c.Assert(err, jc.ErrorIsNil)
  2505  }
  2506  
  2507  type addAliveUnit struct {
  2508  	serviceName string
  2509  	machineId   string
  2510  }
  2511  
  2512  func (aau addAliveUnit) step(c *gc.C, ctx *context) {
  2513  	s, err := ctx.st.Service(aau.serviceName)
  2514  	c.Assert(err, jc.ErrorIsNil)
  2515  	u, err := s.AddUnit()
  2516  	c.Assert(err, jc.ErrorIsNil)
  2517  	pinger := ctx.setAgentPresence(c, u)
  2518  	m, err := ctx.st.Machine(aau.machineId)
  2519  	c.Assert(err, jc.ErrorIsNil)
  2520  	err = u.AssignToMachine(m)
  2521  	c.Assert(err, jc.ErrorIsNil)
  2522  	ctx.pingers[u.Name()] = pinger
  2523  }
  2524  
  2525  type setUnitsAlive struct {
  2526  	serviceName string
  2527  }
  2528  
  2529  func (sua setUnitsAlive) step(c *gc.C, ctx *context) {
  2530  	s, err := ctx.st.Service(sua.serviceName)
  2531  	c.Assert(err, jc.ErrorIsNil)
  2532  	us, err := s.AllUnits()
  2533  	c.Assert(err, jc.ErrorIsNil)
  2534  	for _, u := range us {
  2535  		ctx.pingers[u.Name()] = ctx.setAgentPresence(c, u)
  2536  	}
  2537  }
  2538  
  2539  type setUnitMeterStatus struct {
  2540  	unitName string
  2541  	color    string
  2542  	message  string
  2543  }
  2544  
  2545  func (s setUnitMeterStatus) step(c *gc.C, ctx *context) {
  2546  	u, err := ctx.st.Unit(s.unitName)
  2547  	c.Assert(err, jc.ErrorIsNil)
  2548  	err = u.SetMeterStatus(s.color, s.message)
  2549  	c.Assert(err, jc.ErrorIsNil)
  2550  }
  2551  
  2552  type setUnitStatus struct {
  2553  	unitName   string
  2554  	status     state.Status
  2555  	statusInfo string
  2556  	statusData map[string]interface{}
  2557  }
  2558  
  2559  func (sus setUnitStatus) step(c *gc.C, ctx *context) {
  2560  	u, err := ctx.st.Unit(sus.unitName)
  2561  	c.Assert(err, jc.ErrorIsNil)
  2562  	err = u.SetStatus(sus.status, sus.statusInfo, sus.statusData)
  2563  	c.Assert(err, jc.ErrorIsNil)
  2564  }
  2565  
  2566  type setAgentStatus struct {
  2567  	unitName   string
  2568  	status     state.Status
  2569  	statusInfo string
  2570  	statusData map[string]interface{}
  2571  }
  2572  
  2573  func (sus setAgentStatus) step(c *gc.C, ctx *context) {
  2574  	u, err := ctx.st.Unit(sus.unitName)
  2575  	c.Assert(err, jc.ErrorIsNil)
  2576  	err = u.SetAgentStatus(sus.status, sus.statusInfo, sus.statusData)
  2577  	c.Assert(err, jc.ErrorIsNil)
  2578  }
  2579  
  2580  type setUnitCharmURL struct {
  2581  	unitName string
  2582  	charm    string
  2583  }
  2584  
  2585  func (uc setUnitCharmURL) step(c *gc.C, ctx *context) {
  2586  	u, err := ctx.st.Unit(uc.unitName)
  2587  	c.Assert(err, jc.ErrorIsNil)
  2588  	curl := charm.MustParseURL(uc.charm)
  2589  	err = u.SetCharmURL(curl)
  2590  	c.Assert(err, jc.ErrorIsNil)
  2591  	err = u.SetStatus(state.StatusActive, "", nil)
  2592  	c.Assert(err, jc.ErrorIsNil)
  2593  	err = u.SetAgentStatus(state.StatusIdle, "", nil)
  2594  	c.Assert(err, jc.ErrorIsNil)
  2595  
  2596  }
  2597  
  2598  type openUnitPort struct {
  2599  	unitName string
  2600  	protocol string
  2601  	number   int
  2602  }
  2603  
  2604  func (oup openUnitPort) step(c *gc.C, ctx *context) {
  2605  	u, err := ctx.st.Unit(oup.unitName)
  2606  	c.Assert(err, jc.ErrorIsNil)
  2607  	err = u.OpenPort(oup.protocol, oup.number)
  2608  	c.Assert(err, jc.ErrorIsNil)
  2609  }
  2610  
  2611  type ensureDyingUnit struct {
  2612  	unitName string
  2613  }
  2614  
  2615  func (e ensureDyingUnit) step(c *gc.C, ctx *context) {
  2616  	u, err := ctx.st.Unit(e.unitName)
  2617  	c.Assert(err, jc.ErrorIsNil)
  2618  	err = u.Destroy()
  2619  	c.Assert(err, jc.ErrorIsNil)
  2620  	c.Assert(u.Life(), gc.Equals, state.Dying)
  2621  }
  2622  
  2623  type ensureDyingService struct {
  2624  	serviceName string
  2625  }
  2626  
  2627  func (e ensureDyingService) step(c *gc.C, ctx *context) {
  2628  	svc, err := ctx.st.Service(e.serviceName)
  2629  	c.Assert(err, jc.ErrorIsNil)
  2630  	err = svc.Destroy()
  2631  	c.Assert(err, jc.ErrorIsNil)
  2632  	err = svc.Refresh()
  2633  	c.Assert(err, jc.ErrorIsNil)
  2634  	c.Assert(svc.Life(), gc.Equals, state.Dying)
  2635  }
  2636  
  2637  type ensureDeadMachine struct {
  2638  	machineId string
  2639  }
  2640  
  2641  func (e ensureDeadMachine) step(c *gc.C, ctx *context) {
  2642  	m, err := ctx.st.Machine(e.machineId)
  2643  	c.Assert(err, jc.ErrorIsNil)
  2644  	err = m.EnsureDead()
  2645  	c.Assert(err, jc.ErrorIsNil)
  2646  	c.Assert(m.Life(), gc.Equals, state.Dead)
  2647  }
  2648  
  2649  type setMachineStatus struct {
  2650  	machineId  string
  2651  	status     state.Status
  2652  	statusInfo string
  2653  }
  2654  
  2655  func (sms setMachineStatus) step(c *gc.C, ctx *context) {
  2656  	m, err := ctx.st.Machine(sms.machineId)
  2657  	c.Assert(err, jc.ErrorIsNil)
  2658  	err = m.SetStatus(sms.status, sms.statusInfo, nil)
  2659  	c.Assert(err, jc.ErrorIsNil)
  2660  }
  2661  
  2662  type relateServices struct {
  2663  	ep1, ep2 string
  2664  }
  2665  
  2666  func (rs relateServices) step(c *gc.C, ctx *context) {
  2667  	eps, err := ctx.st.InferEndpoints(rs.ep1, rs.ep2)
  2668  	c.Assert(err, jc.ErrorIsNil)
  2669  	_, err = ctx.st.AddRelation(eps...)
  2670  	c.Assert(err, jc.ErrorIsNil)
  2671  }
  2672  
  2673  type addSubordinate struct {
  2674  	prinUnit   string
  2675  	subService string
  2676  }
  2677  
  2678  func (as addSubordinate) step(c *gc.C, ctx *context) {
  2679  	u, err := ctx.st.Unit(as.prinUnit)
  2680  	c.Assert(err, jc.ErrorIsNil)
  2681  	eps, err := ctx.st.InferEndpoints(u.ServiceName(), as.subService)
  2682  	c.Assert(err, jc.ErrorIsNil)
  2683  	rel, err := ctx.st.EndpointsRelation(eps...)
  2684  	c.Assert(err, jc.ErrorIsNil)
  2685  	ru, err := rel.Unit(u)
  2686  	c.Assert(err, jc.ErrorIsNil)
  2687  	err = ru.EnterScope(nil)
  2688  	c.Assert(err, jc.ErrorIsNil)
  2689  }
  2690  
  2691  type scopedExpect struct {
  2692  	what   string
  2693  	scope  []string
  2694  	output M
  2695  }
  2696  
  2697  type expect struct {
  2698  	what   string
  2699  	output M
  2700  }
  2701  
  2702  // substituteFakeTime replaces all "since" values
  2703  // in actual status output with a known fake value.
  2704  func substituteFakeSinceTime(c *gc.C, in []byte, expectIsoTime bool) []byte {
  2705  	// This regexp will work for yaml and json.
  2706  	exp := regexp.MustCompile(`(?P<since>"?since"?:\ ?)(?P<quote>"?)(?P<timestamp>[^("|\n)]*)*"?`)
  2707  	// Before the substritution is done, check that the timestamp produced
  2708  	// by status is in the correct format.
  2709  	if matches := exp.FindStringSubmatch(string(in)); matches != nil {
  2710  		for i, name := range exp.SubexpNames() {
  2711  			if name != "timestamp" {
  2712  				continue
  2713  			}
  2714  			timeFormat := "02 Jan 2006 15:04:05Z07:00"
  2715  			if expectIsoTime {
  2716  				timeFormat = "2006-01-02 15:04:05Z"
  2717  			}
  2718  			_, err := time.Parse(timeFormat, matches[i])
  2719  			c.Assert(err, jc.ErrorIsNil)
  2720  		}
  2721  	}
  2722  
  2723  	out := exp.ReplaceAllString(string(in), `$since$quote<timestamp>$quote`)
  2724  	// Substitute a made up time used in our expected output.
  2725  	out = strings.Replace(out, "<timestamp>", "01 Apr 15 01:23+10:00", -1)
  2726  	return []byte(out)
  2727  }
  2728  
  2729  func (e scopedExpect) step(c *gc.C, ctx *context) {
  2730  	c.Logf("\nexpect: %s %s\n", e.what, strings.Join(e.scope, " "))
  2731  
  2732  	// Now execute the command for each format.
  2733  	for _, format := range statusFormats {
  2734  		c.Logf("format %q", format.name)
  2735  		// Run command with the required format.
  2736  		args := []string{"--format", format.name}
  2737  		if ctx.expectIsoTime {
  2738  			args = append(args, "--utc")
  2739  		}
  2740  		args = append(args, e.scope...)
  2741  		c.Logf("running status %s", strings.Join(args, " "))
  2742  		code, stdout, stderr := runStatus(c, args...)
  2743  		c.Assert(code, gc.Equals, 0)
  2744  		if !c.Check(stderr, gc.HasLen, 0) {
  2745  			c.Fatalf("status failed: %s", string(stderr))
  2746  		}
  2747  
  2748  		// Prepare the output in the same format.
  2749  		buf, err := format.marshal(e.output)
  2750  		c.Assert(err, jc.ErrorIsNil)
  2751  		expected := make(M)
  2752  		err = format.unmarshal(buf, &expected)
  2753  		c.Assert(err, jc.ErrorIsNil)
  2754  
  2755  		// Check the output is as expected.
  2756  		actual := make(M)
  2757  		out := substituteFakeSinceTime(c, stdout, ctx.expectIsoTime)
  2758  		err = format.unmarshal(out, &actual)
  2759  		c.Assert(err, jc.ErrorIsNil)
  2760  		c.Assert(actual, jc.DeepEquals, expected)
  2761  	}
  2762  }
  2763  
  2764  func (e expect) step(c *gc.C, ctx *context) {
  2765  	scopedExpect{e.what, nil, e.output}.step(c, ctx)
  2766  }
  2767  
  2768  func (s *StatusSuite) TestStatusAllFormats(c *gc.C) {
  2769  	for i, t := range statusTests {
  2770  		c.Logf("test %d: %s", i, t.summary)
  2771  		func(t testCase) {
  2772  			// Prepare context and run all steps to setup.
  2773  			ctx := s.newContext(c)
  2774  			defer s.resetContext(c, ctx)
  2775  			ctx.run(c, t.steps)
  2776  		}(t)
  2777  	}
  2778  }
  2779  
  2780  type fakeApiClient struct {
  2781  	statusReturn *params.FullStatus
  2782  	patternsUsed []string
  2783  	closeCalled  bool
  2784  }
  2785  
  2786  func newFakeApiClient(statusReturn *params.FullStatus) fakeApiClient {
  2787  	return fakeApiClient{
  2788  		statusReturn: statusReturn,
  2789  	}
  2790  }
  2791  
  2792  func (a *fakeApiClient) Status(patterns []string) (*params.FullStatus, error) {
  2793  	a.patternsUsed = patterns
  2794  	return a.statusReturn, nil
  2795  }
  2796  
  2797  func (a *fakeApiClient) Close() error {
  2798  	a.closeCalled = true
  2799  	return nil
  2800  }
  2801  
  2802  // Check that the client works with an older server which doesn't
  2803  // return the top level Relations field nor the unit and machine level
  2804  // Agent field (they were introduced at the same time).
  2805  func (s *StatusSuite) TestStatusWithPreRelationsServer(c *gc.C) {
  2806  	// Construct an older style status response
  2807  	client := newFakeApiClient(&params.FullStatus{
  2808  		EnvironmentName: "dummyenv",
  2809  		Machines: map[string]params.MachineStatus{
  2810  			"0": {
  2811  				// Agent field intentionally not set
  2812  				Id:             "0",
  2813  				InstanceId:     instance.Id("dummyenv-0"),
  2814  				AgentState:     "down",
  2815  				AgentStateInfo: "(started)",
  2816  				Series:         "quantal",
  2817  				Containers:     map[string]params.MachineStatus{},
  2818  				Jobs:           []multiwatcher.MachineJob{multiwatcher.JobManageEnviron},
  2819  				HasVote:        false,
  2820  				WantsVote:      true,
  2821  			},
  2822  			"1": {
  2823  				// Agent field intentionally not set
  2824  				Id:             "1",
  2825  				InstanceId:     instance.Id("dummyenv-1"),
  2826  				AgentState:     "started",
  2827  				AgentStateInfo: "hello",
  2828  				Series:         "quantal",
  2829  				Containers:     map[string]params.MachineStatus{},
  2830  				Jobs:           []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
  2831  				HasVote:        false,
  2832  				WantsVote:      false,
  2833  			},
  2834  		},
  2835  		Services: map[string]params.ServiceStatus{
  2836  			"mysql": {
  2837  				Charm: "local:quantal/mysql-1",
  2838  				Relations: map[string][]string{
  2839  					"server": {"wordpress"},
  2840  				},
  2841  				Units: map[string]params.UnitStatus{
  2842  					"mysql/0": {
  2843  						// Agent field intentionally not set
  2844  						Machine:    "1",
  2845  						AgentState: "allocating",
  2846  					},
  2847  				},
  2848  			},
  2849  			"wordpress": {
  2850  				Charm: "local:quantal/wordpress-3",
  2851  				Relations: map[string][]string{
  2852  					"db": {"mysql"},
  2853  				},
  2854  				Units: map[string]params.UnitStatus{
  2855  					"wordpress/0": {
  2856  						// Agent field intentionally not set
  2857  						AgentState:     "error",
  2858  						AgentStateInfo: "blam",
  2859  						Machine:        "1",
  2860  					},
  2861  				},
  2862  			},
  2863  		},
  2864  		Networks: map[string]params.NetworkStatus{},
  2865  		// Relations field intentionally not set
  2866  	})
  2867  	s.PatchValue(&newApiClientForStatus, func(_ *StatusCommand) (statusAPI, error) {
  2868  		return &client, nil
  2869  	})
  2870  
  2871  	expected := expect{
  2872  		"sane output with an older client that doesn't return Agent or Relations fields",
  2873  		M{
  2874  			"environment": "dummyenv",
  2875  			"machines": M{
  2876  				"0": M{
  2877  					"agent-state":                "down",
  2878  					"agent-state-info":           "(started)",
  2879  					"instance-id":                "dummyenv-0",
  2880  					"series":                     "quantal",
  2881  					"state-server-member-status": "adding-vote",
  2882  				},
  2883  				"1": M{
  2884  					"agent-state":      "started",
  2885  					"agent-state-info": "hello",
  2886  					"instance-id":      "dummyenv-1",
  2887  					"series":           "quantal",
  2888  				},
  2889  			},
  2890  			"services": M{
  2891  				"mysql": M{
  2892  					"charm":   "local:quantal/mysql-1",
  2893  					"exposed": false,
  2894  					"relations": M{
  2895  						"server": L{"wordpress"},
  2896  					},
  2897  					"service-status": M{},
  2898  					"units": M{
  2899  						"mysql/0": M{
  2900  							"machine":         "1",
  2901  							"agent-state":     "allocating",
  2902  							"workload-status": M{},
  2903  							"agent-status":    M{},
  2904  						},
  2905  					},
  2906  				},
  2907  				"wordpress": M{
  2908  					"charm":   "local:quantal/wordpress-3",
  2909  					"exposed": false,
  2910  					"relations": M{
  2911  						"db": L{"mysql"},
  2912  					},
  2913  					"service-status": M{},
  2914  					"units": M{
  2915  						"wordpress/0": M{
  2916  							"machine":          "1",
  2917  							"agent-state":      "error",
  2918  							"agent-state-info": "blam",
  2919  							"workload-status":  M{},
  2920  							"agent-status":     M{},
  2921  						},
  2922  					},
  2923  				},
  2924  			},
  2925  		},
  2926  	}
  2927  	ctx := s.newContext(c)
  2928  	defer s.resetContext(c, ctx)
  2929  	ctx.run(c, []stepper{expected})
  2930  }
  2931  
  2932  func (s *StatusSuite) TestStatusWithFormatSummary(c *gc.C) {
  2933  	ctx := s.newContext(c)
  2934  	defer s.resetContext(c, ctx)
  2935  	steps := []stepper{
  2936  		addMachine{machineId: "0", job: state.JobManageEnviron},
  2937  		setAddresses{"0", network.NewAddresses("localhost")},
  2938  		startAliveMachine{"0"},
  2939  		setMachineStatus{"0", state.StatusStarted, ""},
  2940  		addCharm{"wordpress"},
  2941  		addCharm{"mysql"},
  2942  		addCharm{"logging"},
  2943  		addService{name: "wordpress", charm: "wordpress"},
  2944  		setServiceExposed{"wordpress", true},
  2945  		addMachine{machineId: "1", job: state.JobHostUnits},
  2946  		setAddresses{"1", network.NewAddresses("localhost")},
  2947  		startAliveMachine{"1"},
  2948  		setMachineStatus{"1", state.StatusStarted, ""},
  2949  		addAliveUnit{"wordpress", "1"},
  2950  		setAgentStatus{"wordpress/0", state.StatusIdle, "", nil},
  2951  		setUnitStatus{"wordpress/0", state.StatusActive, "", nil},
  2952  		addService{name: "mysql", charm: "mysql"},
  2953  		setServiceExposed{"mysql", true},
  2954  		addMachine{machineId: "2", job: state.JobHostUnits},
  2955  		setAddresses{"2", network.NewAddresses("10.0.0.1")},
  2956  		startAliveMachine{"2"},
  2957  		setMachineStatus{"2", state.StatusStarted, ""},
  2958  		addAliveUnit{"mysql", "2"},
  2959  		setAgentStatus{"mysql/0", state.StatusIdle, "", nil},
  2960  		setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  2961  		addService{name: "logging", charm: "logging"},
  2962  		setServiceExposed{"logging", true},
  2963  		relateServices{"wordpress", "mysql"},
  2964  		relateServices{"wordpress", "logging"},
  2965  		relateServices{"mysql", "logging"},
  2966  		addSubordinate{"wordpress/0", "logging"},
  2967  		addSubordinate{"mysql/0", "logging"},
  2968  		setUnitsAlive{"logging"},
  2969  		setAgentStatus{"logging/0", state.StatusIdle, "", nil},
  2970  		setUnitStatus{"logging/0", state.StatusActive, "", nil},
  2971  		setAgentStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil},
  2972  	}
  2973  	for _, s := range steps {
  2974  		s.step(c, ctx)
  2975  	}
  2976  	code, stdout, stderr := runStatus(c, "--format", "summary")
  2977  	c.Check(code, gc.Equals, 0)
  2978  	c.Check(string(stderr), gc.Equals, "")
  2979  	c.Assert(
  2980  		string(stdout),
  2981  		gc.Equals,
  2982  		"Running on subnets: 127.0.0.1/8, 10.0.0.1/8 \n"+
  2983  			"Utilizing ports:                            \n"+
  2984  			" # MACHINES: (3)\n"+
  2985  			"    started:  3 \n"+
  2986  			"            \n"+
  2987  			"    # UNITS: (4)\n"+
  2988  			"      error:  1 \n"+
  2989  			"    started:  3 \n"+
  2990  			"            \n"+
  2991  			" # SERVICES:  (3)\n"+
  2992  			"     logging  1/1 exposed\n"+
  2993  			"       mysql  1/1 exposed\n"+
  2994  			"   wordpress  1/1 exposed\n"+
  2995  			"\n",
  2996  	)
  2997  }
  2998  func (s *StatusSuite) TestStatusWithFormatOneline(c *gc.C) {
  2999  	ctx := s.newContext(c)
  3000  	defer s.resetContext(c, ctx)
  3001  	steps := []stepper{
  3002  		addMachine{machineId: "0", job: state.JobManageEnviron},
  3003  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  3004  		startAliveMachine{"0"},
  3005  		setMachineStatus{"0", state.StatusStarted, ""},
  3006  		addCharm{"wordpress"},
  3007  		addCharm{"mysql"},
  3008  		addCharm{"logging"},
  3009  
  3010  		addService{name: "wordpress", charm: "wordpress"},
  3011  		setServiceExposed{"wordpress", true},
  3012  		addMachine{machineId: "1", job: state.JobHostUnits},
  3013  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  3014  		startAliveMachine{"1"},
  3015  		setMachineStatus{"1", state.StatusStarted, ""},
  3016  		addAliveUnit{"wordpress", "1"},
  3017  		setAgentStatus{"wordpress/0", state.StatusIdle, "", nil},
  3018  		setUnitStatus{"wordpress/0", state.StatusActive, "", nil},
  3019  
  3020  		addService{name: "mysql", charm: "mysql"},
  3021  		setServiceExposed{"mysql", true},
  3022  		addMachine{machineId: "2", job: state.JobHostUnits},
  3023  		setAddresses{"2", network.NewAddresses("dummyenv-2.dns")},
  3024  		startAliveMachine{"2"},
  3025  		setMachineStatus{"2", state.StatusStarted, ""},
  3026  		addAliveUnit{"mysql", "2"},
  3027  		setAgentStatus{"mysql/0", state.StatusIdle, "", nil},
  3028  		setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  3029  
  3030  		addService{name: "logging", charm: "logging"},
  3031  		setServiceExposed{"logging", true},
  3032  
  3033  		relateServices{"wordpress", "mysql"},
  3034  		relateServices{"wordpress", "logging"},
  3035  		relateServices{"mysql", "logging"},
  3036  
  3037  		addSubordinate{"wordpress/0", "logging"},
  3038  		addSubordinate{"mysql/0", "logging"},
  3039  
  3040  		setUnitsAlive{"logging"},
  3041  		setAgentStatus{"logging/0", state.StatusIdle, "", nil},
  3042  		setUnitStatus{"logging/0", state.StatusActive, "", nil},
  3043  		setAgentStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil},
  3044  	}
  3045  
  3046  	ctx.run(c, steps)
  3047  
  3048  	const expectedV1 = `
  3049  - mysql/0: dummyenv-2.dns (started)
  3050    - logging/1: dummyenv-2.dns (error)
  3051  - wordpress/0: dummyenv-1.dns (started)
  3052    - logging/0: dummyenv-1.dns (started)
  3053  `
  3054  	assertOneLineStatus(c, expectedV1)
  3055  
  3056  	const expectedV2 = `
  3057  - mysql/0: dummyenv-2.dns (agent:idle, workload:active)
  3058    - logging/1: dummyenv-2.dns (agent:idle, workload:error)
  3059  - wordpress/0: dummyenv-1.dns (agent:idle, workload:active)
  3060    - logging/0: dummyenv-1.dns (agent:idle, workload:active)
  3061  `
  3062  	s.PatchEnvironment(osenv.JujuCLIVersion, "2")
  3063  	assertOneLineStatus(c, expectedV2)
  3064  }
  3065  
  3066  func assertOneLineStatus(c *gc.C, expected string) {
  3067  	code, stdout, stderr := runStatus(c, "--format", "oneline")
  3068  	c.Check(code, gc.Equals, 0)
  3069  	c.Check(string(stderr), gc.Equals, "")
  3070  	c.Assert(string(stdout), gc.Equals, expected)
  3071  
  3072  	c.Log(`Check that "short" is an alias for oneline.`)
  3073  	code, stdout, stderr = runStatus(c, "--format", "short")
  3074  	c.Check(code, gc.Equals, 0)
  3075  	c.Check(string(stderr), gc.Equals, "")
  3076  	c.Assert(string(stdout), gc.Equals, expected)
  3077  
  3078  	c.Log(`Check that "line" is an alias for oneline.`)
  3079  	code, stdout, stderr = runStatus(c, "--format", "line")
  3080  	c.Check(code, gc.Equals, 0)
  3081  	c.Check(string(stderr), gc.Equals, "")
  3082  	c.Assert(string(stdout), gc.Equals, expected)
  3083  }
  3084  
  3085  func (s *StatusSuite) prepareTabularData(c *gc.C) *context {
  3086  	ctx := s.newContext(c)
  3087  	steps := []stepper{
  3088  		addMachine{machineId: "0", job: state.JobManageEnviron},
  3089  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  3090  		startAliveMachine{"0"},
  3091  		setMachineStatus{"0", state.StatusStarted, ""},
  3092  		addCharm{"wordpress"},
  3093  		addCharm{"mysql"},
  3094  		addCharm{"logging"},
  3095  		addService{name: "wordpress", charm: "wordpress"},
  3096  		setServiceExposed{"wordpress", true},
  3097  		addMachine{machineId: "1", job: state.JobHostUnits},
  3098  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  3099  		startAliveMachine{"1"},
  3100  		setMachineStatus{"1", state.StatusStarted, ""},
  3101  		addAliveUnit{"wordpress", "1"},
  3102  		setAgentStatus{"wordpress/0", state.StatusIdle, "", nil},
  3103  		setUnitStatus{"wordpress/0", state.StatusActive, "", nil},
  3104  		setUnitTools{"wordpress/0", version.MustParseBinary("1.2.3-trusty-ppc")},
  3105  		addService{name: "mysql", charm: "mysql"},
  3106  		setServiceExposed{"mysql", true},
  3107  		addMachine{machineId: "2", job: state.JobHostUnits},
  3108  		setAddresses{"2", network.NewAddresses("dummyenv-2.dns")},
  3109  		startAliveMachine{"2"},
  3110  		setMachineStatus{"2", state.StatusStarted, ""},
  3111  		addAliveUnit{"mysql", "2"},
  3112  		setAgentStatus{"mysql/0", state.StatusIdle, "", nil},
  3113  		setUnitStatus{
  3114  			"mysql/0",
  3115  			state.StatusMaintenance,
  3116  			"installing all the things", nil},
  3117  		setUnitTools{"mysql/0", version.MustParseBinary("1.2.3-trusty-ppc")},
  3118  		addService{name: "logging", charm: "logging"},
  3119  		setServiceExposed{"logging", true},
  3120  		relateServices{"wordpress", "mysql"},
  3121  		relateServices{"wordpress", "logging"},
  3122  		relateServices{"mysql", "logging"},
  3123  		addSubordinate{"wordpress/0", "logging"},
  3124  		addSubordinate{"mysql/0", "logging"},
  3125  		setUnitsAlive{"logging"},
  3126  		setAgentStatus{"logging/0", state.StatusIdle, "", nil},
  3127  		setUnitStatus{"logging/0", state.StatusActive, "", nil},
  3128  		setAgentStatus{"logging/1", state.StatusError, "somehow lost in all those logs", nil},
  3129  	}
  3130  	for _, s := range steps {
  3131  		s.step(c, ctx)
  3132  	}
  3133  	return ctx
  3134  }
  3135  
  3136  func (s *StatusSuite) testStatusWithFormatTabular(c *gc.C, useFeatureFlag bool) {
  3137  	ctx := s.prepareTabularData(c)
  3138  	defer s.resetContext(c, ctx)
  3139  	var args []string
  3140  	if !useFeatureFlag {
  3141  		args = []string{"--format", "tabular"}
  3142  	}
  3143  	code, stdout, stderr := runStatus(c, args...)
  3144  	c.Check(code, gc.Equals, 0)
  3145  	c.Check(string(stderr), gc.Equals, "")
  3146  	c.Assert(
  3147  		string(stdout),
  3148  		gc.Equals,
  3149  		"[Services] \n"+
  3150  			"NAME       STATUS      EXPOSED CHARM                  \n"+
  3151  			"logging                true    cs:quantal/logging-1   \n"+
  3152  			"mysql      maintenance true    cs:quantal/mysql-1     \n"+
  3153  			"wordpress  active      true    cs:quantal/wordpress-3 \n"+
  3154  			"\n"+
  3155  			"[Units]     \n"+
  3156  			"ID          WORKLOAD-STATE AGENT-STATE VERSION MACHINE PORTS PUBLIC-ADDRESS MESSAGE                        \n"+
  3157  			"mysql/0     maintenance    idle        1.2.3   2             dummyenv-2.dns installing all the things      \n"+
  3158  			"  logging/1 error          idle                              dummyenv-2.dns somehow lost in all those logs \n"+
  3159  			"wordpress/0 active         idle        1.2.3   1             dummyenv-1.dns                                \n"+
  3160  			"  logging/0 active         idle                              dummyenv-1.dns                                \n"+
  3161  			"\n"+
  3162  			"[Machines] \n"+
  3163  			"ID         STATE   VERSION DNS            INS-ID     SERIES  HARDWARE                                         \n"+
  3164  			"0          started         dummyenv-0.dns dummyenv-0 quantal arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M \n"+
  3165  			"1          started         dummyenv-1.dns dummyenv-1 quantal arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M \n"+
  3166  			"2          started         dummyenv-2.dns dummyenv-2 quantal arch=amd64 cpu-cores=1 mem=1024M root-disk=8192M \n"+
  3167  			"\n",
  3168  	)
  3169  }
  3170  
  3171  func (s *StatusSuite) TestStatusV2(c *gc.C) {
  3172  	s.PatchEnvironment(osenv.JujuCLIVersion, "2")
  3173  	s.testStatusWithFormatTabular(c, true)
  3174  }
  3175  
  3176  func (s *StatusSuite) TestStatusWithFormatTabular(c *gc.C) {
  3177  	s.testStatusWithFormatTabular(c, false)
  3178  }
  3179  
  3180  func (s *StatusSuite) TestFormatTabularHookActionName(c *gc.C) {
  3181  	status := formattedStatus{
  3182  		Services: map[string]serviceStatus{
  3183  			"foo": serviceStatus{
  3184  				Units: map[string]unitStatus{
  3185  					"foo/0": unitStatus{
  3186  						AgentStatusInfo: statusInfoContents{
  3187  							Current: params.StatusExecuting,
  3188  							Message: "running config-changed hook",
  3189  						},
  3190  						WorkloadStatusInfo: statusInfoContents{
  3191  							Current: params.StatusMaintenance,
  3192  							Message: "doing some work",
  3193  						},
  3194  					},
  3195  					"foo/1": unitStatus{
  3196  						AgentStatusInfo: statusInfoContents{
  3197  							Current: params.StatusExecuting,
  3198  							Message: "running action backup database",
  3199  						},
  3200  						WorkloadStatusInfo: statusInfoContents{
  3201  							Current: params.StatusMaintenance,
  3202  							Message: "doing some work",
  3203  						},
  3204  					},
  3205  				},
  3206  			},
  3207  		},
  3208  	}
  3209  	out, err := FormatTabular(status)
  3210  	c.Assert(err, jc.ErrorIsNil)
  3211  	c.Assert(
  3212  		string(out),
  3213  		gc.Equals,
  3214  		"[Services] \n"+
  3215  			"NAME       STATUS EXPOSED CHARM \n"+
  3216  			"foo               false         \n"+
  3217  			"\n"+
  3218  			"[Units] \n"+
  3219  			"ID      WORKLOAD-STATE AGENT-STATE VERSION MACHINE PORTS PUBLIC-ADDRESS MESSAGE                           \n"+
  3220  			"foo/0   maintenance    executing                                        (config-changed) doing some work  \n"+
  3221  			"foo/1   maintenance    executing                                        (backup database) doing some work \n"+
  3222  			"\n"+
  3223  			"[Machines] \n"+
  3224  			"ID         STATE VERSION DNS INS-ID SERIES HARDWARE \n",
  3225  	)
  3226  }
  3227  
  3228  func (s *StatusSuite) TestStatusWithNilStatusApi(c *gc.C) {
  3229  	ctx := s.newContext(c)
  3230  	defer s.resetContext(c, ctx)
  3231  	steps := []stepper{
  3232  		addMachine{machineId: "0", job: state.JobManageEnviron},
  3233  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  3234  		startAliveMachine{"0"},
  3235  		setMachineStatus{"0", state.StatusStarted, ""},
  3236  	}
  3237  
  3238  	for _, s := range steps {
  3239  		s.step(c, ctx)
  3240  	}
  3241  
  3242  	client := fakeApiClient{}
  3243  	var status = client.Status
  3244  	s.PatchValue(&status, func(_ []string) (*params.FullStatus, error) {
  3245  		return nil, nil
  3246  	})
  3247  	s.PatchValue(&newApiClientForStatus, func(_ *StatusCommand) (statusAPI, error) {
  3248  		return &client, nil
  3249  	})
  3250  
  3251  	code, _, stderr := runStatus(c, "--format", "tabular")
  3252  	c.Check(code, gc.Equals, 1)
  3253  	c.Check(string(stderr), gc.Equals, "error: unable to obtain the current status\n")
  3254  }
  3255  
  3256  //
  3257  // Filtering Feature
  3258  //
  3259  
  3260  func (s *StatusSuite) FilteringTestSetup(c *gc.C) *context {
  3261  	ctx := s.newContext(c)
  3262  
  3263  	steps := []stepper{
  3264  		// Given a machine is started
  3265  		// And the machine's ID is "0"
  3266  		// And the machine's job is to manage the environment
  3267  		addMachine{machineId: "0", job: state.JobManageEnviron},
  3268  		startAliveMachine{"0"},
  3269  		setMachineStatus{"0", state.StatusStarted, ""},
  3270  		// And the machine's address is "dummyenv-0.dns"
  3271  		setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  3272  		// And the "wordpress" charm is available
  3273  		addCharm{"wordpress"},
  3274  		addService{name: "wordpress", charm: "wordpress"},
  3275  		// And the "mysql" charm is available
  3276  		addCharm{"mysql"},
  3277  		addService{name: "mysql", charm: "mysql"},
  3278  		// And the "logging" charm is available
  3279  		addCharm{"logging"},
  3280  		// And a machine is started
  3281  		// And the machine's ID is "1"
  3282  		// And the machine's job is to host units
  3283  		addMachine{machineId: "1", job: state.JobHostUnits},
  3284  		startAliveMachine{"1"},
  3285  		setMachineStatus{"1", state.StatusStarted, ""},
  3286  		// And the machine's address is "dummyenv-1.dns"
  3287  		setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  3288  		// And a unit of "wordpress" is deployed to machine "1"
  3289  		addAliveUnit{"wordpress", "1"},
  3290  		// And the unit is started
  3291  		setAgentStatus{"wordpress/0", state.StatusIdle, "", nil},
  3292  		setUnitStatus{"wordpress/0", state.StatusActive, "", nil},
  3293  		// And a machine is started
  3294  
  3295  		// And the machine's ID is "2"
  3296  		// And the machine's job is to host units
  3297  		addMachine{machineId: "2", job: state.JobHostUnits},
  3298  		startAliveMachine{"2"},
  3299  		setMachineStatus{"2", state.StatusStarted, ""},
  3300  		// And the machine's address is "dummyenv-2.dns"
  3301  		setAddresses{"2", network.NewAddresses("dummyenv-2.dns")},
  3302  		// And a unit of "mysql" is deployed to machine "2"
  3303  		addAliveUnit{"mysql", "2"},
  3304  		// And the unit is started
  3305  		setAgentStatus{"mysql/0", state.StatusIdle, "", nil},
  3306  		setUnitStatus{"mysql/0", state.StatusActive, "", nil},
  3307  		// And the "logging" service is added
  3308  		addService{name: "logging", charm: "logging"},
  3309  		// And the service is exposed
  3310  		setServiceExposed{"logging", true},
  3311  		// And the "wordpress" service is related to the "mysql" service
  3312  		relateServices{"wordpress", "mysql"},
  3313  		// And the "wordpress" service is related to the "logging" service
  3314  		relateServices{"wordpress", "logging"},
  3315  		// And the "mysql" service is related to the "logging" service
  3316  		relateServices{"mysql", "logging"},
  3317  		// And the "logging" service is a subordinate to unit 0 of the "wordpress" service
  3318  		addSubordinate{"wordpress/0", "logging"},
  3319  		setAgentStatus{"logging/0", state.StatusIdle, "", nil},
  3320  		setUnitStatus{"logging/0", state.StatusActive, "", nil},
  3321  		// And the "logging" service is a subordinate to unit 0 of the "mysql" service
  3322  		addSubordinate{"mysql/0", "logging"},
  3323  		setAgentStatus{"logging/1", state.StatusIdle, "", nil},
  3324  		setUnitStatus{"logging/1", state.StatusActive, "", nil},
  3325  		setUnitsAlive{"logging"},
  3326  	}
  3327  
  3328  	ctx.run(c, steps)
  3329  	return ctx
  3330  }
  3331  
  3332  // Scenario: One unit is in an errored state and user filters to started
  3333  func (s *StatusSuite) TestFilterToStarted(c *gc.C) {
  3334  	ctx := s.FilteringTestSetup(c)
  3335  	defer s.resetContext(c, ctx)
  3336  
  3337  	// Given unit 1 of the "logging" service has an error
  3338  	setAgentStatus{"logging/1", state.StatusError, "mock error", nil}.step(c, ctx)
  3339  	// And unit 0 of the "mysql" service has an error
  3340  	setAgentStatus{"mysql/0", state.StatusError, "mock error", nil}.step(c, ctx)
  3341  	// When I run juju status --format oneline started
  3342  	_, stdout, stderr := runStatus(c, "--format", "oneline", "started")
  3343  	c.Assert(string(stderr), gc.Equals, "")
  3344  	// Then I should receive output prefixed with:
  3345  	const expected = `
  3346  
  3347  - wordpress/0: dummyenv-1.dns (started)
  3348    - logging/0: dummyenv-1.dns (started)
  3349  `
  3350  
  3351  	c.Assert(string(stdout), gc.Equals, expected[1:])
  3352  }
  3353  
  3354  // Scenario: One unit is in an errored state and user filters to errored
  3355  func (s *StatusSuite) TestFilterToErrored(c *gc.C) {
  3356  	ctx := s.FilteringTestSetup(c)
  3357  	defer s.resetContext(c, ctx)
  3358  
  3359  	// Given unit 1 of the "logging" service has an error
  3360  	setAgentStatus{"logging/1", state.StatusError, "mock error", nil}.step(c, ctx)
  3361  	// When I run juju status --format oneline error
  3362  	_, stdout, stderr := runStatus(c, "--format", "oneline", "error")
  3363  	c.Assert(stderr, gc.IsNil)
  3364  	// Then I should receive output prefixed with:
  3365  	const expected = `
  3366  
  3367  - mysql/0: dummyenv-2.dns (started)
  3368    - logging/1: dummyenv-2.dns (error)
  3369  `
  3370  
  3371  	c.Assert(string(stdout), gc.Equals, expected[1:])
  3372  }
  3373  
  3374  // Scenario: User filters to mysql service
  3375  func (s *StatusSuite) TestFilterToService(c *gc.C) {
  3376  	ctx := s.FilteringTestSetup(c)
  3377  	defer s.resetContext(c, ctx)
  3378  
  3379  	// When I run juju status --format oneline error
  3380  	_, stdout, stderr := runStatus(c, "--format", "oneline", "mysql")
  3381  	c.Assert(stderr, gc.IsNil)
  3382  	// Then I should receive output prefixed with:
  3383  	const expected = `
  3384  
  3385  - mysql/0: dummyenv-2.dns (started)
  3386    - logging/1: dummyenv-2.dns (started)
  3387  `
  3388  
  3389  	c.Assert(string(stdout), gc.Equals, expected[1:])
  3390  }
  3391  
  3392  // Scenario: User filters to exposed services
  3393  func (s *StatusSuite) TestFilterToExposedService(c *gc.C) {
  3394  	ctx := s.FilteringTestSetup(c)
  3395  	defer s.resetContext(c, ctx)
  3396  
  3397  	// Given unit 1 of the "mysql" service is exposed
  3398  	setServiceExposed{"mysql", true}.step(c, ctx)
  3399  	// And the logging service is not exposed
  3400  	setServiceExposed{"logging", false}.step(c, ctx)
  3401  	// And the wordpress service is not exposed
  3402  	setServiceExposed{"wordpress", false}.step(c, ctx)
  3403  	// When I run juju status --format oneline exposed
  3404  	_, stdout, stderr := runStatus(c, "--format", "oneline", "exposed")
  3405  	c.Assert(stderr, gc.IsNil)
  3406  	// Then I should receive output prefixed with:
  3407  	const expected = `
  3408  
  3409  - mysql/0: dummyenv-2.dns (started)
  3410    - logging/1: dummyenv-2.dns (started)
  3411  `
  3412  
  3413  	c.Assert(string(stdout), gc.Equals, expected[1:])
  3414  }
  3415  
  3416  // Scenario: User filters to non-exposed services
  3417  func (s *StatusSuite) TestFilterToNotExposedService(c *gc.C) {
  3418  	ctx := s.FilteringTestSetup(c)
  3419  	defer s.resetContext(c, ctx)
  3420  
  3421  	setServiceExposed{"mysql", true}.step(c, ctx)
  3422  	// When I run juju status --format oneline not exposed
  3423  	_, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed")
  3424  	c.Assert(stderr, gc.IsNil)
  3425  	// Then I should receive output prefixed with:
  3426  	const expected = `
  3427  
  3428  - wordpress/0: dummyenv-1.dns (started)
  3429    - logging/0: dummyenv-1.dns (started)
  3430  `
  3431  
  3432  	c.Assert(string(stdout), gc.Equals, expected[1:])
  3433  }
  3434  
  3435  // Scenario: Filtering on Subnets
  3436  func (s *StatusSuite) TestFilterOnSubnet(c *gc.C) {
  3437  	ctx := s.FilteringTestSetup(c)
  3438  	defer s.resetContext(c, ctx)
  3439  
  3440  	// Given the address for machine "1" is "localhost"
  3441  	setAddresses{"1", network.NewAddresses("localhost")}.step(c, ctx)
  3442  	// And the address for machine "2" is "10.0.0.1"
  3443  	setAddresses{"2", network.NewAddresses("10.0.0.1")}.step(c, ctx)
  3444  	// When I run juju status --format oneline 127.0.0.1
  3445  	_, stdout, stderr := runStatus(c, "--format", "oneline", "127.0.0.1")
  3446  	c.Assert(stderr, gc.IsNil)
  3447  	// Then I should receive output prefixed with:
  3448  	const expected = `
  3449  
  3450  - wordpress/0: localhost (started)
  3451    - logging/0: localhost (started)
  3452  `
  3453  
  3454  	c.Assert(string(stdout), gc.Equals, expected[1:])
  3455  }
  3456  
  3457  // Scenario: Filtering on Ports
  3458  func (s *StatusSuite) TestFilterOnPorts(c *gc.C) {
  3459  	ctx := s.FilteringTestSetup(c)
  3460  	defer s.resetContext(c, ctx)
  3461  
  3462  	// Given the address for machine "1" is "localhost"
  3463  	setAddresses{"1", network.NewAddresses("localhost")}.step(c, ctx)
  3464  	// And the address for machine "2" is "10.0.0.1"
  3465  	setAddresses{"2", network.NewAddresses("10.0.0.1")}.step(c, ctx)
  3466  	openUnitPort{"wordpress/0", "tcp", 80}.step(c, ctx)
  3467  	// When I run juju status --format oneline 80/tcp
  3468  	_, stdout, stderr := runStatus(c, "--format", "oneline", "80/tcp")
  3469  	c.Assert(stderr, gc.IsNil)
  3470  	// Then I should receive output prefixed with:
  3471  	const expected = `
  3472  
  3473  - wordpress/0: localhost (started) 80/tcp
  3474    - logging/0: localhost (started)
  3475  `
  3476  
  3477  	c.Assert(string(stdout), gc.Equals, expected[1:])
  3478  }
  3479  
  3480  // Scenario: User filters out a parent, but not its subordinate
  3481  func (s *StatusSuite) TestFilterParentButNotSubordinate(c *gc.C) {
  3482  	ctx := s.FilteringTestSetup(c)
  3483  	defer s.resetContext(c, ctx)
  3484  
  3485  	// When I run juju status --format oneline 80/tcp
  3486  	_, stdout, stderr := runStatus(c, "--format", "oneline", "logging")
  3487  	c.Assert(stderr, gc.IsNil)
  3488  	// Then I should receive output prefixed with:
  3489  	const expected = `
  3490  
  3491  - mysql/0: dummyenv-2.dns (started)
  3492    - logging/1: dummyenv-2.dns (started)
  3493  - wordpress/0: dummyenv-1.dns (started)
  3494    - logging/0: dummyenv-1.dns (started)
  3495  `
  3496  
  3497  	c.Assert(string(stdout), gc.Equals, expected[1:])
  3498  }
  3499  
  3500  // Scenario: User filters out a subordinate, but not its parent
  3501  func (s *StatusSuite) TestFilterSubordinateButNotParent(c *gc.C) {
  3502  	ctx := s.FilteringTestSetup(c)
  3503  	defer s.resetContext(c, ctx)
  3504  
  3505  	// Given the wordpress service is exposed
  3506  	setServiceExposed{"wordpress", true}.step(c, ctx)
  3507  	// When I run juju status --format oneline not exposed
  3508  	_, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed")
  3509  	c.Assert(stderr, gc.IsNil)
  3510  	// Then I should receive output prefixed with:
  3511  	const expected = `
  3512  
  3513  - mysql/0: dummyenv-2.dns (started)
  3514    - logging/1: dummyenv-2.dns (started)
  3515  `
  3516  
  3517  	c.Assert(string(stdout), gc.Equals, expected[1:])
  3518  }
  3519  
  3520  func (s *StatusSuite) TestFilterMultipleHomogenousPatterns(c *gc.C) {
  3521  	ctx := s.FilteringTestSetup(c)
  3522  	defer s.resetContext(c, ctx)
  3523  
  3524  	_, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "mysql/0")
  3525  	c.Assert(stderr, gc.IsNil)
  3526  	// Then I should receive output prefixed with:
  3527  	const expected = `
  3528  
  3529  - mysql/0: dummyenv-2.dns (started)
  3530    - logging/1: dummyenv-2.dns (started)
  3531  - wordpress/0: dummyenv-1.dns (started)
  3532    - logging/0: dummyenv-1.dns (started)
  3533  `
  3534  
  3535  	c.Assert(string(stdout), gc.Equals, expected[1:])
  3536  }
  3537  
  3538  func (s *StatusSuite) TestFilterMultipleHeterogenousPatterns(c *gc.C) {
  3539  	ctx := s.FilteringTestSetup(c)
  3540  	defer s.resetContext(c, ctx)
  3541  
  3542  	_, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "started")
  3543  	c.Assert(stderr, gc.IsNil)
  3544  	// Then I should receive output prefixed with:
  3545  	const expected = `
  3546  
  3547  - mysql/0: dummyenv-2.dns (started)
  3548    - logging/1: dummyenv-2.dns (started)
  3549  - wordpress/0: dummyenv-1.dns (started)
  3550    - logging/0: dummyenv-1.dns (started)
  3551  `
  3552  
  3553  	c.Assert(string(stdout), gc.Equals, expected[1:])
  3554  }
  3555  
  3556  // TestSummaryStatusWithUnresolvableDns is result of bug# 1410320.
  3557  func (s *StatusSuite) TestSummaryStatusWithUnresolvableDns(c *gc.C) {
  3558  	formatter := &summaryFormatter{}
  3559  	formatter.resolveAndTrackIp("invalidDns")
  3560  	// Test should not panic.
  3561  }
  3562  
  3563  func initStatusCommand(args ...string) (*StatusCommand, error) {
  3564  	com := &StatusCommand{}
  3565  	return com, coretesting.InitCommand(envcmd.Wrap(com), args)
  3566  }
  3567  
  3568  var statusInitTests = []struct {
  3569  	args    []string
  3570  	envVar  string
  3571  	isoTime bool
  3572  	err     string
  3573  }{
  3574  	{
  3575  		isoTime: false,
  3576  	}, {
  3577  		args:    []string{"--utc"},
  3578  		isoTime: true,
  3579  	}, {
  3580  		envVar:  "true",
  3581  		isoTime: true,
  3582  	}, {
  3583  		envVar: "foo",
  3584  		err:    "invalid JUJU_STATUS_ISO_TIME env var, expected true|false.*",
  3585  	},
  3586  }
  3587  
  3588  func (*StatusSuite) TestStatusCommandInit(c *gc.C) {
  3589  	defer os.Setenv(osenv.JujuStatusIsoTimeEnvKey, os.Getenv(osenv.JujuStatusIsoTimeEnvKey))
  3590  
  3591  	for i, t := range statusInitTests {
  3592  		c.Logf("test %d", i)
  3593  		os.Setenv(osenv.JujuStatusIsoTimeEnvKey, t.envVar)
  3594  		com, err := initStatusCommand(t.args...)
  3595  		if t.err != "" {
  3596  			c.Check(err, gc.ErrorMatches, t.err)
  3597  		} else {
  3598  			c.Check(err, jc.ErrorIsNil)
  3599  		}
  3600  		c.Check(com.isoTime, gc.DeepEquals, t.isoTime)
  3601  	}
  3602  }
  3603  
  3604  var statusTimeTest = test(
  3605  	"status generates timestamps as UTC in ISO format",
  3606  	addMachine{machineId: "0", job: state.JobManageEnviron},
  3607  	setAddresses{"0", network.NewAddresses("dummyenv-0.dns")},
  3608  	startAliveMachine{"0"},
  3609  	setMachineStatus{"0", state.StatusStarted, ""},
  3610  	addCharm{"dummy"},
  3611  	addService{name: "dummy-service", charm: "dummy"},
  3612  
  3613  	addMachine{machineId: "1", job: state.JobHostUnits},
  3614  	startAliveMachine{"1"},
  3615  	setAddresses{"1", network.NewAddresses("dummyenv-1.dns")},
  3616  	setMachineStatus{"1", state.StatusStarted, ""},
  3617  
  3618  	addAliveUnit{"dummy-service", "1"},
  3619  	expect{
  3620  		"add two units, one alive (in error state), one started",
  3621  		M{
  3622  			"environment": "dummyenv",
  3623  			"machines": M{
  3624  				"0": machine0,
  3625  				"1": machine1,
  3626  			},
  3627  			"services": M{
  3628  				"dummy-service": M{
  3629  					"charm":   "cs:quantal/dummy-1",
  3630  					"exposed": false,
  3631  					"service-status": M{
  3632  						"current": "unknown",
  3633  						"message": "Waiting for agent initialization to finish",
  3634  						"since":   "01 Apr 15 01:23+10:00",
  3635  					},
  3636  					"units": M{
  3637  						"dummy-service/0": M{
  3638  							"machine":     "1",
  3639  							"agent-state": "pending",
  3640  							"workload-status": M{
  3641  								"current": "unknown",
  3642  								"message": "Waiting for agent initialization to finish",
  3643  								"since":   "01 Apr 15 01:23+10:00",
  3644  							},
  3645  							"agent-status": M{
  3646  								"current": "allocating",
  3647  								"since":   "01 Apr 15 01:23+10:00",
  3648  							},
  3649  							"public-address": "dummyenv-1.dns",
  3650  						},
  3651  					},
  3652  				},
  3653  			},
  3654  		},
  3655  	},
  3656  )
  3657  
  3658  func (s *StatusSuite) TestIsoTimeFormat(c *gc.C) {
  3659  	func(t testCase) {
  3660  		// Prepare context and run all steps to setup.
  3661  		ctx := s.newContext(c)
  3662  		ctx.expectIsoTime = true
  3663  		defer s.resetContext(c, ctx)
  3664  		ctx.run(c, t.steps)
  3665  	}(statusTimeTest)
  3666  }