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