github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/status/status_test.go (about)

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