github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/cmd/juju/commands/status_test.go (about)

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