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