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