github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/status/status_internal_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  	"io/ioutil"
    11  	"os"
    12  	"regexp"
    13  	"runtime"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/juju/cmd"
    18  	"github.com/juju/cmd/cmdtesting"
    19  	"github.com/juju/loggo"
    20  	jc "github.com/juju/testing/checkers"
    21  	"github.com/juju/utils"
    22  	"github.com/juju/version"
    23  	"github.com/kr/pretty"
    24  	gc "gopkg.in/check.v1"
    25  	"gopkg.in/juju/charm.v6"
    26  	"gopkg.in/juju/names.v2"
    27  	goyaml "gopkg.in/yaml.v2"
    28  
    29  	"github.com/juju/juju/apiserver/params"
    30  	"github.com/juju/juju/cmd/juju/common"
    31  	"github.com/juju/juju/cmd/modelcmd"
    32  	"github.com/juju/juju/core/constraints"
    33  	"github.com/juju/juju/core/crossmodel"
    34  	"github.com/juju/juju/core/instance"
    35  	"github.com/juju/juju/core/lease"
    36  	"github.com/juju/juju/core/migration"
    37  	corepresence "github.com/juju/juju/core/presence"
    38  	"github.com/juju/juju/core/status"
    39  	"github.com/juju/juju/environs"
    40  	environscontext "github.com/juju/juju/environs/context"
    41  	"github.com/juju/juju/juju/osenv"
    42  	"github.com/juju/juju/juju/testing"
    43  	"github.com/juju/juju/network"
    44  	"github.com/juju/juju/state"
    45  	"github.com/juju/juju/state/multiwatcher"
    46  	"github.com/juju/juju/state/presence"
    47  	"github.com/juju/juju/testcharms"
    48  	coretesting "github.com/juju/juju/testing"
    49  	"github.com/juju/juju/testing/factory"
    50  	coreversion "github.com/juju/juju/version"
    51  )
    52  
    53  var (
    54  	currentVersion = version.Number{Major: 1, Minor: 2, Patch: 3}
    55  	nextVersion    = version.Number{Major: 1, Minor: 2, Patch: 4}
    56  )
    57  
    58  func runStatus(c *gc.C, args ...string) (code int, stdout, stderr []byte) {
    59  	ctx := cmdtesting.Context(c)
    60  	code = cmd.Main(NewStatusCommand(), ctx, args)
    61  	stdout = ctx.Stdout.(*bytes.Buffer).Bytes()
    62  	stderr = ctx.Stderr.(*bytes.Buffer).Bytes()
    63  	return
    64  }
    65  
    66  type StatusSuite struct {
    67  	testing.JujuConnSuite
    68  }
    69  
    70  var _ = gc.Suite(&StatusSuite{})
    71  
    72  func (s *StatusSuite) SetUpSuite(c *gc.C) {
    73  	s.JujuConnSuite.SetUpSuite(c)
    74  	s.PatchValue(&coreversion.Current, currentVersion)
    75  }
    76  
    77  func (s *StatusSuite) SetUpTest(c *gc.C) {
    78  	s.ConfigAttrs = map[string]interface{}{
    79  		"agent-version": currentVersion.String(),
    80  	}
    81  	s.JujuConnSuite.SetUpTest(c)
    82  }
    83  
    84  type M map[string]interface{}
    85  
    86  type L []interface{}
    87  
    88  type testCase struct {
    89  	summary string
    90  	steps   []stepper
    91  }
    92  
    93  func test(summary string, steps ...stepper) testCase {
    94  	return testCase{summary, steps}
    95  }
    96  
    97  type stepper interface {
    98  	step(c *gc.C, ctx *context)
    99  }
   100  
   101  //
   102  // context
   103  //
   104  
   105  func newContext(st *state.State, pool *state.StatePool, env environs.Environ, adminUserTag string) *context {
   106  	// We make changes in the API server's state so that
   107  	// our changes to presence are immediately noticed
   108  	// in the status.
   109  	return &context{
   110  		st:           st,
   111  		pool:         pool,
   112  		env:          env,
   113  		statusSetter: env.(agentStatusSetter),
   114  		charms:       make(map[string]*state.Charm),
   115  		pingers:      make(map[string]*presence.Pinger),
   116  		adminUserTag: adminUserTag,
   117  	}
   118  }
   119  
   120  type agentStatusSetter interface {
   121  	SetAgentStatus(agent string, status corepresence.Status)
   122  }
   123  
   124  type context struct {
   125  	st            *state.State
   126  	pool          *state.StatePool
   127  	env           environs.Environ
   128  	statusSetter  agentStatusSetter
   129  	charms        map[string]*state.Charm
   130  	pingers       map[string]*presence.Pinger
   131  	adminUserTag  string // A string repr of the tag.
   132  	expectIsoTime bool
   133  	skipTest      bool
   134  }
   135  
   136  func (ctx *context) reset(c *gc.C) {
   137  	for _, up := range ctx.pingers {
   138  		err := up.KillForTesting()
   139  		c.Check(err, jc.ErrorIsNil)
   140  	}
   141  }
   142  
   143  func (ctx *context) run(c *gc.C, steps []stepper) {
   144  	for i, s := range steps {
   145  		if ctx.skipTest {
   146  			c.Logf("skipping test %d", i)
   147  			return
   148  		}
   149  		c.Logf("step %d", i)
   150  		c.Logf("%#v", s)
   151  		s.step(c, ctx)
   152  	}
   153  }
   154  
   155  func (ctx *context) setAgentPresence(c *gc.C, p presence.Agent) *presence.Pinger {
   156  	pinger, err := p.SetAgentPresence()
   157  	c.Assert(err, jc.ErrorIsNil)
   158  	ctx.st.StartSync()
   159  	err = p.WaitAgentPresence(coretesting.LongWait)
   160  	c.Assert(err, jc.ErrorIsNil)
   161  	agentPresence, err := p.AgentPresence()
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	c.Assert(agentPresence, jc.IsTrue)
   164  	return pinger
   165  }
   166  
   167  func (s *StatusSuite) newContext(c *gc.C) *context {
   168  	st := s.Environ.(testing.GetStater).GetStateInAPIServer()
   169  
   170  	// We make changes in the API server's state so that
   171  	// our changes to presence are immediately noticed
   172  	// in the status.
   173  	return newContext(st, s.StatePool, s.Environ, s.AdminUserTag(c).String())
   174  }
   175  
   176  func (s *StatusSuite) resetContext(c *gc.C, ctx *context) {
   177  	ctx.reset(c)
   178  	s.JujuConnSuite.Reset(c)
   179  }
   180  
   181  // shortcuts for expected output.
   182  var (
   183  	model = M{
   184  		"name":       "controller",
   185  		"type":       "iaas",
   186  		"controller": "kontroll",
   187  		"cloud":      "dummy",
   188  		"region":     "dummy-region",
   189  		"version":    "1.2.3",
   190  		"model-status": M{
   191  			"current": "available",
   192  			"since":   "01 Apr 15 01:23+10:00",
   193  		},
   194  		"sla": "unsupported",
   195  	}
   196  
   197  	machine0 = M{
   198  		"juju-status": M{
   199  			"current": "started",
   200  			"since":   "01 Apr 15 01:23+10:00",
   201  		},
   202  		"dns-name":     "10.0.0.1",
   203  		"ip-addresses": []string{"10.0.0.1"},
   204  		"instance-id":  "controller-0",
   205  		"machine-status": M{
   206  			"current": "pending",
   207  			"since":   "01 Apr 15 01:23+10:00",
   208  		},
   209  		"series": "quantal",
   210  		"network-interfaces": M{
   211  			"eth0": M{
   212  				"ip-addresses": []string{"10.0.0.1"},
   213  				"mac-address":  "aa:bb:cc:dd:ee:ff",
   214  				"is-up":        true,
   215  			},
   216  		},
   217  		"hardware":                 "arch=amd64 cores=1 mem=1024M root-disk=8192M",
   218  		"controller-member-status": "adding-vote",
   219  	}
   220  	machine1 = M{
   221  		"juju-status": M{
   222  			"current": "started",
   223  			"since":   "01 Apr 15 01:23+10:00",
   224  		},
   225  		"dns-name":     "10.0.1.1",
   226  		"ip-addresses": []string{"10.0.1.1"},
   227  		"instance-id":  "controller-1",
   228  		"machine-status": M{
   229  			"current": "pending",
   230  			"since":   "01 Apr 15 01:23+10:00",
   231  		},
   232  		"series": "quantal",
   233  		"network-interfaces": M{
   234  			"eth0": M{
   235  				"ip-addresses": []string{"10.0.1.1"},
   236  				"mac-address":  "aa:bb:cc:dd:ee:ff",
   237  				"is-up":        true,
   238  			},
   239  		},
   240  		"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
   241  	}
   242  	machine1WithLXDProfile = M{
   243  		"juju-status": M{
   244  			"current": "started",
   245  			"since":   "01 Apr 15 01:23+10:00",
   246  		},
   247  		"dns-name":     "10.0.1.1",
   248  		"ip-addresses": []string{"10.0.1.1"},
   249  		"instance-id":  "controller-1",
   250  		"machine-status": M{
   251  			"current": "pending",
   252  			"since":   "01 Apr 15 01:23+10:00",
   253  		},
   254  		"series": "quantal",
   255  		"network-interfaces": M{
   256  			"eth0": M{
   257  				"ip-addresses": []string{"10.0.1.1"},
   258  				"mac-address":  "aa:bb:cc:dd:ee:ff",
   259  				"is-up":        true,
   260  			},
   261  		},
   262  		"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
   263  		"lxd-profiles": M{
   264  			"juju-controller-lxd-profile-1": M{
   265  				"config": M{
   266  					"environment.http_proxy": "",
   267  					"linux.kernel_modules":   "openvswitch,nbd,ip_tables,ip6_tables",
   268  					"security.nesting":       "true",
   269  					"security.privileged":    "true",
   270  				},
   271  				"description": "lxd profile for testing, will pass validation",
   272  				"devices": M{
   273  					"bdisk": M{
   274  						"source": "/dev/loop0",
   275  						"type":   "unix-block",
   276  					},
   277  					"gpu": M{
   278  						"type": "gpu",
   279  					},
   280  					"sony": M{
   281  						"productid": "51da",
   282  						"type":      "usb",
   283  						"vendorid":  "0fce",
   284  					},
   285  					"tun": M{
   286  						"path": "/dev/net/tun",
   287  						"type": "unix-char",
   288  					},
   289  				},
   290  			},
   291  		},
   292  	}
   293  	machine2 = M{
   294  		"juju-status": M{
   295  			"current": "started",
   296  			"since":   "01 Apr 15 01:23+10:00",
   297  		},
   298  		"dns-name":     "10.0.2.1",
   299  		"ip-addresses": []string{"10.0.2.1"},
   300  		"instance-id":  "controller-2",
   301  		"machine-status": M{
   302  			"current": "pending",
   303  			"since":   "01 Apr 15 01:23+10:00",
   304  		},
   305  		"series": "quantal",
   306  		"network-interfaces": M{
   307  			"eth0": M{
   308  				"ip-addresses": []string{"10.0.2.1"},
   309  				"mac-address":  "aa:bb:cc:dd:ee:ff",
   310  				"is-up":        true,
   311  			},
   312  		},
   313  		"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
   314  	}
   315  	machine3 = M{
   316  		"juju-status": M{
   317  			"current": "started",
   318  			"since":   "01 Apr 15 01:23+10:00",
   319  		},
   320  		"dns-name":     "10.0.3.1",
   321  		"ip-addresses": []string{"10.0.3.1"},
   322  		"instance-id":  "controller-3",
   323  		"machine-status": M{
   324  			"current": "started",
   325  			"message": "I am number three",
   326  			"since":   "01 Apr 15 01:23+10:00",
   327  		},
   328  		"series": "quantal",
   329  		"network-interfaces": M{
   330  			"eth0": M{
   331  				"ip-addresses": []string{"10.0.3.1"},
   332  				"mac-address":  "aa:bb:cc:dd:ee:ff",
   333  				"is-up":        true,
   334  			},
   335  		},
   336  		"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
   337  	}
   338  	machine4 = M{
   339  		"juju-status": M{
   340  			"current": "started",
   341  			"since":   "01 Apr 15 01:23+10:00",
   342  		},
   343  		"dns-name":     "10.0.4.1",
   344  		"ip-addresses": []string{"10.0.4.1"},
   345  		"instance-id":  "controller-4",
   346  		"machine-status": M{
   347  			"current": "pending",
   348  			"since":   "01 Apr 15 01:23+10:00",
   349  		},
   350  		"series": "quantal",
   351  		"network-interfaces": M{
   352  			"eth0": M{
   353  				"ip-addresses": []string{"10.0.4.1"},
   354  				"mac-address":  "aa:bb:cc:dd:ee:ff",
   355  				"is-up":        true,
   356  			},
   357  		},
   358  		"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
   359  	}
   360  	machine1WithContainers = M{
   361  		"juju-status": M{
   362  			"current": "started",
   363  			"since":   "01 Apr 15 01:23+10:00",
   364  		},
   365  		"containers": M{
   366  			"1/lxd/0": M{
   367  				"juju-status": M{
   368  					"current": "started",
   369  					"since":   "01 Apr 15 01:23+10:00",
   370  				},
   371  				"containers": M{
   372  					"1/lxd/0/lxd/0": M{
   373  						"juju-status": M{
   374  							"current": "started",
   375  							"since":   "01 Apr 15 01:23+10:00",
   376  						},
   377  						"dns-name":     "10.0.3.1",
   378  						"ip-addresses": []string{"10.0.3.1"},
   379  						"instance-id":  "controller-3",
   380  						"machine-status": M{
   381  							"current": "pending",
   382  							"since":   "01 Apr 15 01:23+10:00",
   383  						},
   384  						"series": "quantal",
   385  						"network-interfaces": M{
   386  							"eth0": M{
   387  								"ip-addresses": []string{"10.0.3.1"},
   388  								"mac-address":  "aa:bb:cc:dd:ee:ff",
   389  								"is-up":        true,
   390  							},
   391  						},
   392  					},
   393  				},
   394  				"dns-name":     "10.0.2.1",
   395  				"ip-addresses": []string{"10.0.2.1"},
   396  				"instance-id":  "controller-2",
   397  				"machine-status": M{
   398  					"current": "pending",
   399  					"since":   "01 Apr 15 01:23+10:00",
   400  				},
   401  				"series": "quantal",
   402  				"network-interfaces": M{
   403  					"eth0": M{
   404  						"ip-addresses": []string{"10.0.2.1"},
   405  						"mac-address":  "aa:bb:cc:dd:ee:ff",
   406  						"is-up":        true,
   407  					},
   408  				},
   409  			},
   410  			"1/lxd/1": M{
   411  				"juju-status": M{
   412  					"current": "pending",
   413  					"since":   "01 Apr 15 01:23+10:00",
   414  				},
   415  				"instance-id": "pending",
   416  				"machine-status": M{
   417  					"current": "pending",
   418  					"since":   "01 Apr 15 01:23+10:00",
   419  				},
   420  				"series": "quantal",
   421  			},
   422  		},
   423  		"dns-name":     "10.0.1.1",
   424  		"ip-addresses": []string{"10.0.1.1"},
   425  		"instance-id":  "controller-1",
   426  		"machine-status": M{
   427  			"current": "pending",
   428  			"since":   "01 Apr 15 01:23+10:00",
   429  		},
   430  		"series": "quantal",
   431  		"network-interfaces": M{
   432  			"eth0": M{
   433  				"ip-addresses": []string{"10.0.1.1"},
   434  				"mac-address":  "aa:bb:cc:dd:ee:ff",
   435  				"is-up":        true,
   436  			},
   437  		},
   438  		"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
   439  	}
   440  	unexposedApplication = dummyCharm(M{
   441  		"application-status": M{
   442  			"current": "waiting",
   443  			"message": "waiting for machine",
   444  			"since":   "01 Apr 15 01:23+10:00",
   445  		},
   446  	})
   447  	exposedApplication = dummyCharm(M{
   448  		"application-status": M{
   449  			"current": "waiting",
   450  			"message": "waiting for machine",
   451  			"since":   "01 Apr 15 01:23+10:00",
   452  		},
   453  		"exposed": true,
   454  	})
   455  	loggingCharm = M{
   456  		"charm":        "cs:quantal/logging-1",
   457  		"charm-origin": "jujucharms",
   458  		"charm-name":   "logging",
   459  		"charm-rev":    1,
   460  		"series":       "quantal",
   461  		"os":           "ubuntu",
   462  		"exposed":      true,
   463  		"application-status": M{
   464  			"current": "error",
   465  			"message": "somehow lost in all those logs",
   466  			"since":   "01 Apr 15 01:23+10:00",
   467  		},
   468  		"relations": M{
   469  			"logging-directory": L{"wordpress"},
   470  			"info":              L{"mysql"},
   471  		},
   472  		"endpoint-bindings": M{
   473  			"info":              "",
   474  			"logging-client":    "",
   475  			"logging-directory": "",
   476  		},
   477  		"subordinate-to": L{"mysql", "wordpress"},
   478  	}
   479  )
   480  
   481  type outputFormat struct {
   482  	name      string
   483  	marshal   func(v interface{}) ([]byte, error)
   484  	unmarshal func(data []byte, v interface{}) error
   485  }
   486  
   487  // statusFormats list all output formats that can be marshalled as structured data,
   488  // supported by status command.
   489  var statusFormats = []outputFormat{
   490  	{"yaml", goyaml.Marshal, goyaml.Unmarshal},
   491  	{"json", json.Marshal, json.Unmarshal},
   492  }
   493  
   494  var machineCons = constraints.MustParse("cores=2 mem=8G root-disk=8G")
   495  
   496  var statusTests = []testCase{
   497  	// Status tests
   498  	test( // 0
   499  		"bootstrap and starting a single instance",
   500  
   501  		addMachine{machineId: "0", job: state.JobManageModel},
   502  		expect{
   503  			what: "simulate juju bootstrap by adding machine/0 to the state",
   504  			output: M{
   505  				"model": model,
   506  				"machines": M{
   507  					"0": M{
   508  						"juju-status": M{
   509  							"current": "pending",
   510  							"since":   "01 Apr 15 01:23+10:00",
   511  						},
   512  						"instance-id": "pending",
   513  						"machine-status": M{
   514  							"current": "pending",
   515  							"since":   "01 Apr 15 01:23+10:00",
   516  						},
   517  						"series":                   "quantal",
   518  						"controller-member-status": "adding-vote",
   519  					},
   520  				},
   521  				"applications": M{},
   522  				"storage":      M{},
   523  				"controller": M{
   524  					"timestamp": "15:04:05+07:00",
   525  				},
   526  			},
   527  		},
   528  
   529  		startAliveMachine{"0", ""},
   530  		setAddresses{"0", []network.Address{
   531  			network.NewScopedAddress("10.0.0.1", network.ScopePublic),
   532  			network.NewAddress("10.0.0.2"),
   533  		}},
   534  		expect{
   535  			what: "simulate the PA starting an instance in response to the state change",
   536  			output: M{
   537  				"model": model,
   538  				"machines": M{
   539  					"0": M{
   540  						"juju-status": M{
   541  							"current": "pending",
   542  							"since":   "01 Apr 15 01:23+10:00",
   543  						},
   544  						"dns-name":     "10.0.0.1",
   545  						"ip-addresses": []string{"10.0.0.1", "10.0.0.2"},
   546  						"instance-id":  "controller-0",
   547  						"machine-status": M{
   548  							"current": "pending",
   549  							"since":   "01 Apr 15 01:23+10:00",
   550  						},
   551  						"series": "quantal",
   552  						"network-interfaces": M{
   553  							"eth0": M{
   554  								"ip-addresses": []string{"10.0.0.1"},
   555  								"mac-address":  "aa:bb:cc:dd:ee:ff",
   556  								"is-up":        true,
   557  							},
   558  							"eth1": M{
   559  								"ip-addresses": []string{"10.0.0.2"},
   560  								"mac-address":  "aa:bb:cc:dd:ee:ff",
   561  								"is-up":        true,
   562  							},
   563  						},
   564  						"hardware":                 "arch=amd64 cores=1 mem=1024M root-disk=8192M",
   565  						"controller-member-status": "adding-vote",
   566  					},
   567  				},
   568  				"applications": M{},
   569  				"storage":      M{},
   570  				"controller": M{
   571  					"timestamp": "15:04:05+07:00",
   572  				},
   573  			},
   574  		},
   575  
   576  		setMachineStatus{"0", status.Started, ""},
   577  		expect{
   578  			what: "simulate the MA started and set the machine status",
   579  			output: M{
   580  				"model": model,
   581  				"machines": M{
   582  					"0": M{
   583  						"juju-status": M{
   584  							"current": "started",
   585  							"since":   "01 Apr 15 01:23+10:00",
   586  						},
   587  						"dns-name":     "10.0.0.1",
   588  						"ip-addresses": []string{"10.0.0.1", "10.0.0.2"},
   589  						"instance-id":  "controller-0",
   590  						"machine-status": M{
   591  							"current": "pending",
   592  							"since":   "01 Apr 15 01:23+10:00",
   593  						},
   594  						"series": "quantal",
   595  						"network-interfaces": M{
   596  							"eth0": M{
   597  								"ip-addresses": []string{"10.0.0.1"},
   598  								"mac-address":  "aa:bb:cc:dd:ee:ff",
   599  								"is-up":        true,
   600  							},
   601  							"eth1": M{
   602  								"ip-addresses": []string{"10.0.0.2"},
   603  								"mac-address":  "aa:bb:cc:dd:ee:ff",
   604  								"is-up":        true,
   605  							},
   606  						},
   607  						"hardware":                 "arch=amd64 cores=1 mem=1024M root-disk=8192M",
   608  						"controller-member-status": "adding-vote",
   609  					},
   610  				},
   611  				"applications": M{},
   612  				"storage":      M{},
   613  				"controller": M{
   614  					"timestamp": "15:04:05+07:00",
   615  				},
   616  			},
   617  		},
   618  
   619  		setTools{"0", version.MustParseBinary("1.2.3-trusty-ppc")},
   620  		expect{
   621  			what: "simulate the MA setting the version",
   622  			output: M{
   623  				"model": model,
   624  				"machines": M{
   625  					"0": M{
   626  						"dns-name":     "10.0.0.1",
   627  						"ip-addresses": []string{"10.0.0.1", "10.0.0.2"},
   628  						"instance-id":  "controller-0",
   629  						"machine-status": M{
   630  							"current": "pending",
   631  							"since":   "01 Apr 15 01:23+10:00",
   632  						},
   633  						"juju-status": M{
   634  							"current": "started",
   635  							"since":   "01 Apr 15 01:23+10:00",
   636  							"version": "1.2.3",
   637  						},
   638  						"series": "quantal",
   639  						"network-interfaces": M{
   640  							"eth0": M{
   641  								"ip-addresses": []string{"10.0.0.1"},
   642  								"mac-address":  "aa:bb:cc:dd:ee:ff",
   643  								"is-up":        true,
   644  							},
   645  							"eth1": M{
   646  								"ip-addresses": []string{"10.0.0.2"},
   647  								"mac-address":  "aa:bb:cc:dd:ee:ff",
   648  								"is-up":        true,
   649  							},
   650  						},
   651  						"hardware":                 "arch=amd64 cores=1 mem=1024M root-disk=8192M",
   652  						"controller-member-status": "adding-vote",
   653  					},
   654  				},
   655  				"applications": M{},
   656  				"storage":      M{},
   657  				"controller": M{
   658  					"timestamp": "15:04:05+07:00",
   659  				},
   660  			},
   661  		},
   662  	),
   663  	test( // 1
   664  		"instance with different hardware characteristics",
   665  		addMachine{machineId: "0", cons: machineCons, job: state.JobManageModel},
   666  		setAddresses{"0", []network.Address{
   667  			network.NewScopedAddress("10.0.0.1", network.ScopePublic),
   668  			network.NewAddress("10.0.0.2"),
   669  		}},
   670  		startAliveMachine{"0", ""},
   671  		setMachineStatus{"0", status.Started, ""},
   672  		expect{
   673  			what: "machine 0 has specific hardware characteristics",
   674  			output: M{
   675  				"model": model,
   676  				"machines": M{
   677  					"0": M{
   678  						"juju-status": M{
   679  							"current": "started",
   680  							"since":   "01 Apr 15 01:23+10:00",
   681  						},
   682  						"dns-name":     "10.0.0.1",
   683  						"ip-addresses": []string{"10.0.0.1", "10.0.0.2"},
   684  						"instance-id":  "controller-0",
   685  						"machine-status": M{
   686  							"current": "pending",
   687  							"since":   "01 Apr 15 01:23+10:00",
   688  						},
   689  						"series": "quantal",
   690  						"network-interfaces": M{
   691  							"eth0": M{
   692  								"ip-addresses": []string{"10.0.0.1"},
   693  								"mac-address":  "aa:bb:cc:dd:ee:ff",
   694  								"is-up":        true,
   695  							},
   696  							"eth1": M{
   697  								"ip-addresses": []string{"10.0.0.2"},
   698  								"mac-address":  "aa:bb:cc:dd:ee:ff",
   699  								"is-up":        true,
   700  							},
   701  						},
   702  						"constraints":              "cores=2 mem=8192M root-disk=8192M",
   703  						"hardware":                 "arch=amd64 cores=2 mem=8192M root-disk=8192M",
   704  						"controller-member-status": "adding-vote",
   705  					},
   706  				},
   707  				"applications": M{},
   708  				"storage":      M{},
   709  				"controller": M{
   710  					"timestamp": "15:04:05+07:00",
   711  				},
   712  			},
   713  		},
   714  	),
   715  	test( // 2
   716  		"instance without addresses",
   717  		addMachine{machineId: "0", cons: machineCons, job: state.JobManageModel},
   718  		startAliveMachine{"0", ""},
   719  		setMachineStatus{"0", status.Started, ""},
   720  		expect{
   721  			what: "machine 0 has no dns-name",
   722  			output: M{
   723  				"model": model,
   724  				"machines": M{
   725  					"0": M{
   726  						"juju-status": M{
   727  							"current": "started",
   728  							"since":   "01 Apr 15 01:23+10:00",
   729  						},
   730  						"instance-id": "controller-0",
   731  						"machine-status": M{
   732  							"current": "pending",
   733  							"since":   "01 Apr 15 01:23+10:00",
   734  						},
   735  						"series":                   "quantal",
   736  						"constraints":              "cores=2 mem=8192M root-disk=8192M",
   737  						"hardware":                 "arch=amd64 cores=2 mem=8192M root-disk=8192M",
   738  						"controller-member-status": "adding-vote",
   739  					},
   740  				},
   741  				"applications": M{},
   742  				"storage":      M{},
   743  				"controller": M{
   744  					"timestamp": "15:04:05+07:00",
   745  				},
   746  			},
   747  		},
   748  	),
   749  	test( // 3
   750  		"test pending and missing machines",
   751  		addMachine{machineId: "0", job: state.JobManageModel},
   752  		expect{
   753  			what: "machine 0 reports pending",
   754  			output: M{
   755  				"model": model,
   756  				"machines": M{
   757  					"0": M{
   758  						"juju-status": M{
   759  							"current": "pending",
   760  							"since":   "01 Apr 15 01:23+10:00",
   761  						},
   762  						"instance-id": "pending",
   763  						"machine-status": M{
   764  							"current": "pending",
   765  							"since":   "01 Apr 15 01:23+10:00",
   766  						},
   767  						"series":                   "quantal",
   768  						"controller-member-status": "adding-vote",
   769  					},
   770  				},
   771  				"applications": M{},
   772  				"storage":      M{},
   773  				"controller": M{
   774  					"timestamp": "15:04:05+07:00",
   775  				},
   776  			},
   777  		},
   778  
   779  		startMissingMachine{"0"},
   780  		expect{
   781  			what: "machine 0 reports missing",
   782  			output: M{
   783  				"model": model,
   784  				"machines": M{
   785  					"0": M{
   786  						"instance-id": "i-missing",
   787  						"juju-status": M{
   788  							"current": "pending",
   789  							"since":   "01 Apr 15 01:23+10:00",
   790  						},
   791  						"machine-status": M{
   792  							"current": "unknown",
   793  							"message": "missing",
   794  							"since":   "01 Apr 15 01:23+10:00",
   795  						},
   796  						"series":                   "quantal",
   797  						"hardware":                 "arch=amd64 cores=1 mem=1024M root-disk=8192M",
   798  						"controller-member-status": "adding-vote",
   799  					},
   800  				},
   801  				"applications": M{},
   802  				"storage":      M{},
   803  				"controller": M{
   804  					"timestamp": "15:04:05+07:00",
   805  				},
   806  			},
   807  		},
   808  	),
   809  	test( // 4
   810  		"add two applications and expose one, then add 2 more machines and some units",
   811  		// step 0
   812  		addMachine{machineId: "0", job: state.JobManageModel},
   813  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
   814  		startAliveMachine{"0", ""},
   815  		setMachineStatus{"0", status.Started, ""},
   816  		addCharm{"dummy"},
   817  		addApplication{name: "dummy-application", charm: "dummy"},
   818  		addApplication{name: "exposed-application", charm: "dummy"},
   819  		expect{
   820  			what: "no applications exposed yet",
   821  			output: M{
   822  				"model": model,
   823  				"machines": M{
   824  					"0": machine0,
   825  				},
   826  				"applications": M{
   827  					"dummy-application":   unexposedApplication,
   828  					"exposed-application": unexposedApplication,
   829  				},
   830  				"storage": M{},
   831  				"controller": M{
   832  					"timestamp": "15:04:05+07:00",
   833  				},
   834  			},
   835  		},
   836  
   837  		// step 8
   838  		setApplicationExposed{"exposed-application", true},
   839  		expect{
   840  			what: "one exposed application",
   841  			output: M{
   842  				"model": model,
   843  				"machines": M{
   844  					"0": machine0,
   845  				},
   846  				"applications": M{
   847  					"dummy-application":   unexposedApplication,
   848  					"exposed-application": exposedApplication,
   849  				},
   850  				"storage": M{},
   851  				"controller": M{
   852  					"timestamp": "15:04:05+07:00",
   853  				},
   854  			},
   855  		},
   856  
   857  		// step 10
   858  		addMachine{machineId: "1", job: state.JobHostUnits},
   859  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
   860  		startAliveMachine{"1", ""},
   861  		setMachineStatus{"1", status.Started, ""},
   862  		addMachine{machineId: "2", job: state.JobHostUnits},
   863  		setAddresses{"2", network.NewAddresses("10.0.2.1")},
   864  		startAliveMachine{"2", ""},
   865  		setMachineStatus{"2", status.Started, ""},
   866  		expect{
   867  			what: "two more machines added",
   868  			output: M{
   869  				"model": model,
   870  				"machines": M{
   871  					"0": machine0,
   872  					"1": machine1,
   873  					"2": machine2,
   874  				},
   875  				"applications": M{
   876  					"dummy-application":   unexposedApplication,
   877  					"exposed-application": exposedApplication,
   878  				},
   879  				"storage": M{},
   880  				"controller": M{
   881  					"timestamp": "15:04:05+07:00",
   882  				},
   883  			},
   884  		},
   885  
   886  		// step 19
   887  		addAliveUnit{"dummy-application", "1"},
   888  		addAliveUnit{"exposed-application", "2"},
   889  		setAgentStatus{"exposed-application/0", status.Error, "You Require More Vespene Gas", nil},
   890  		// Open multiple ports with different protocols,
   891  		// ensure they're sorted on protocol, then number.
   892  		openUnitPort{"exposed-application/0", "udp", 10},
   893  		openUnitPort{"exposed-application/0", "udp", 2},
   894  		openUnitPort{"exposed-application/0", "tcp", 3},
   895  		openUnitPort{"exposed-application/0", "tcp", 2},
   896  		// Simulate some status with no info, while the agent is down.
   897  		// Status used to be down, we no longer support said state.
   898  		// now is one of: pending, started, error.
   899  		setUnitStatus{"dummy-application/0", status.Terminated, "", nil},
   900  		setAgentStatus{"dummy-application/0", status.Idle, "", nil},
   901  
   902  		expect{
   903  			what: "add two units, one alive (in error state), one started",
   904  			output: M{
   905  				"model": model,
   906  				"machines": M{
   907  					"0": machine0,
   908  					"1": machine1,
   909  					"2": machine2,
   910  				},
   911  				"applications": M{
   912  					"exposed-application": dummyCharm(M{
   913  						"exposed": true,
   914  						"application-status": M{
   915  							"current": "error",
   916  							"message": "You Require More Vespene Gas",
   917  							"since":   "01 Apr 15 01:23+10:00",
   918  						},
   919  						"units": M{
   920  							"exposed-application/0": M{
   921  								"machine": "2",
   922  								"workload-status": M{
   923  									"current": "error",
   924  									"message": "You Require More Vespene Gas",
   925  									"since":   "01 Apr 15 01:23+10:00",
   926  								},
   927  								"juju-status": M{
   928  									"current": "idle",
   929  									"since":   "01 Apr 15 01:23+10:00",
   930  								},
   931  								"open-ports": L{
   932  									"2/tcp", "3/tcp", "2/udp", "10/udp",
   933  								},
   934  								"public-address": "10.0.2.1",
   935  							},
   936  						},
   937  					}),
   938  					"dummy-application": dummyCharm(M{
   939  						"application-status": M{
   940  							"current": "terminated",
   941  							"since":   "01 Apr 15 01:23+10:00",
   942  						},
   943  						"units": M{
   944  							"dummy-application/0": M{
   945  								"machine": "1",
   946  								"workload-status": M{
   947  									"current": "terminated",
   948  									"since":   "01 Apr 15 01:23+10:00",
   949  								},
   950  								"juju-status": M{
   951  									"current": "idle",
   952  									"since":   "01 Apr 15 01:23+10:00",
   953  								},
   954  								"public-address": "10.0.1.1",
   955  							},
   956  						},
   957  					}),
   958  				},
   959  				"storage": M{},
   960  				"controller": M{
   961  					"timestamp": "15:04:05+07:00",
   962  				},
   963  			},
   964  		},
   965  
   966  		// step 29
   967  		addMachine{machineId: "3", job: state.JobHostUnits},
   968  		startMachine{"3"},
   969  		// Simulate some status with info, while the agent is down.
   970  		setAddresses{"3", network.NewAddresses("10.0.3.1")},
   971  		setMachineStatus{"3", status.Stopped, "Really?"},
   972  		addMachine{machineId: "4", job: state.JobHostUnits},
   973  		setAddresses{"4", network.NewAddresses("10.0.4.1")},
   974  		startAliveMachine{"4", ""},
   975  		setMachineStatus{"4", status.Error, "Beware the red toys"},
   976  		ensureDyingUnit{"dummy-application/0"},
   977  		addMachine{machineId: "5", job: state.JobHostUnits},
   978  		ensureDeadMachine{"5"},
   979  		expect{
   980  			what: "add three more machine, one with a dead agent, one in error state and one dead itself; also one dying unit",
   981  			output: M{
   982  				"model": model,
   983  				"machines": M{
   984  					"0": machine0,
   985  					"1": machine1,
   986  					"2": machine2,
   987  					"3": M{
   988  						"dns-name":     "10.0.3.1",
   989  						"ip-addresses": []string{"10.0.3.1"},
   990  						"instance-id":  "controller-3",
   991  						"machine-status": M{
   992  							"current": "pending",
   993  							"since":   "01 Apr 15 01:23+10:00",
   994  						},
   995  						"juju-status": M{
   996  							"current": "stopped",
   997  							"message": "Really?",
   998  							"since":   "01 Apr 15 01:23+10:00",
   999  						},
  1000  						"series": "quantal",
  1001  						"network-interfaces": M{
  1002  							"eth0": M{
  1003  								"ip-addresses": []string{"10.0.3.1"},
  1004  								"mac-address":  "aa:bb:cc:dd:ee:ff",
  1005  								"is-up":        true,
  1006  							},
  1007  						},
  1008  						"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
  1009  					},
  1010  					"4": M{
  1011  						"dns-name":     "10.0.4.1",
  1012  						"ip-addresses": []string{"10.0.4.1"},
  1013  						"instance-id":  "controller-4",
  1014  						"machine-status": M{
  1015  							"current": "pending",
  1016  							"since":   "01 Apr 15 01:23+10:00",
  1017  						},
  1018  						"juju-status": M{
  1019  							"current": "error",
  1020  							"message": "Beware the red toys",
  1021  							"since":   "01 Apr 15 01:23+10:00",
  1022  						},
  1023  						"series": "quantal",
  1024  						"network-interfaces": M{
  1025  							"eth0": M{
  1026  								"ip-addresses": []string{"10.0.4.1"},
  1027  								"mac-address":  "aa:bb:cc:dd:ee:ff",
  1028  								"is-up":        true,
  1029  							},
  1030  						},
  1031  						"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
  1032  					},
  1033  					"5": M{
  1034  						"juju-status": M{
  1035  							"current": "pending",
  1036  							"since":   "01 Apr 15 01:23+10:00",
  1037  							"life":    "dead",
  1038  						},
  1039  						"instance-id": "pending",
  1040  						"machine-status": M{
  1041  							"current": "pending",
  1042  							"since":   "01 Apr 15 01:23+10:00",
  1043  						},
  1044  						"series": "quantal",
  1045  					},
  1046  				},
  1047  				"applications": M{
  1048  					"exposed-application": dummyCharm(M{
  1049  						"exposed": true,
  1050  						"application-status": M{
  1051  							"current": "error",
  1052  							"message": "You Require More Vespene Gas",
  1053  							"since":   "01 Apr 15 01:23+10:00",
  1054  						},
  1055  						"units": M{
  1056  							"exposed-application/0": M{
  1057  								"machine": "2",
  1058  								"workload-status": M{
  1059  									"current": "error",
  1060  									"message": "You Require More Vespene Gas",
  1061  									"since":   "01 Apr 15 01:23+10:00",
  1062  								},
  1063  								"juju-status": M{
  1064  									"current": "idle",
  1065  									"since":   "01 Apr 15 01:23+10:00",
  1066  								},
  1067  								"open-ports": L{
  1068  									"2/tcp", "3/tcp", "2/udp", "10/udp",
  1069  								},
  1070  								"public-address": "10.0.2.1",
  1071  							},
  1072  						},
  1073  					}),
  1074  					"dummy-application": dummyCharm(M{
  1075  						"application-status": M{
  1076  							"current": "terminated",
  1077  							"since":   "01 Apr 15 01:23+10:00",
  1078  						},
  1079  						"units": M{
  1080  							"dummy-application/0": M{
  1081  								"machine": "1",
  1082  								"workload-status": M{
  1083  									"current": "terminated",
  1084  									"since":   "01 Apr 15 01:23+10:00",
  1085  								},
  1086  								"juju-status": M{
  1087  									"current": "idle",
  1088  									"since":   "01 Apr 15 01:23+10:00",
  1089  								},
  1090  								"public-address": "10.0.1.1",
  1091  							},
  1092  						},
  1093  					}),
  1094  				},
  1095  				"storage": M{},
  1096  				"controller": M{
  1097  					"timestamp": "15:04:05+07:00",
  1098  				},
  1099  			},
  1100  		},
  1101  
  1102  		// step 41
  1103  		scopedExpect{
  1104  			what:  "scope status on dummy-application/0 unit",
  1105  			scope: []string{"dummy-application/0"},
  1106  			output: M{
  1107  				"model": model,
  1108  				"machines": M{
  1109  					"1": machine1,
  1110  				},
  1111  				"applications": M{
  1112  					"dummy-application": dummyCharm(M{
  1113  						"application-status": M{
  1114  							"current": "terminated",
  1115  							"since":   "01 Apr 15 01:23+10:00",
  1116  						},
  1117  						"units": M{
  1118  							"dummy-application/0": M{
  1119  								"machine": "1",
  1120  								"workload-status": M{
  1121  									"current": "terminated",
  1122  									"since":   "01 Apr 15 01:23+10:00",
  1123  								},
  1124  								"juju-status": M{
  1125  									"current": "idle",
  1126  									"since":   "01 Apr 15 01:23+10:00",
  1127  								},
  1128  								"public-address": "10.0.1.1",
  1129  							},
  1130  						},
  1131  					}),
  1132  				},
  1133  				"storage": M{},
  1134  				"controller": M{
  1135  					"timestamp": "15:04:05+07:00",
  1136  				},
  1137  			},
  1138  		},
  1139  		scopedExpect{
  1140  			what:  "scope status on exposed-application application",
  1141  			scope: []string{"exposed-application"},
  1142  			output: M{
  1143  				"model": model,
  1144  				"machines": M{
  1145  					"2": machine2,
  1146  				},
  1147  				"applications": M{
  1148  					"exposed-application": dummyCharm(M{
  1149  						"exposed": true,
  1150  						"application-status": M{
  1151  							"current": "error",
  1152  							"message": "You Require More Vespene Gas",
  1153  							"since":   "01 Apr 15 01:23+10:00",
  1154  						},
  1155  						"units": M{
  1156  							"exposed-application/0": M{
  1157  								"machine": "2",
  1158  								"workload-status": M{
  1159  									"current": "error",
  1160  									"message": "You Require More Vespene Gas",
  1161  									"since":   "01 Apr 15 01:23+10:00",
  1162  								},
  1163  								"juju-status": M{
  1164  									"current": "idle",
  1165  									"since":   "01 Apr 15 01:23+10:00",
  1166  								},
  1167  								"open-ports": L{
  1168  									"2/tcp", "3/tcp", "2/udp", "10/udp",
  1169  								},
  1170  								"public-address": "10.0.2.1",
  1171  							},
  1172  						},
  1173  					}),
  1174  				},
  1175  				"storage": M{},
  1176  				"controller": M{
  1177  					"timestamp": "15:04:05+07:00",
  1178  				},
  1179  			},
  1180  		},
  1181  		scopedExpect{
  1182  			what:  "scope status on application pattern",
  1183  			scope: []string{"d*-application"},
  1184  			output: M{
  1185  				"model": model,
  1186  				"machines": M{
  1187  					"1": machine1,
  1188  				},
  1189  				"applications": M{
  1190  					"dummy-application": dummyCharm(M{
  1191  						"application-status": M{
  1192  							"current": "terminated",
  1193  							"since":   "01 Apr 15 01:23+10:00",
  1194  						},
  1195  						"units": M{
  1196  							"dummy-application/0": M{
  1197  								"machine": "1",
  1198  								"workload-status": M{
  1199  									"current": "terminated",
  1200  									"since":   "01 Apr 15 01:23+10:00",
  1201  								},
  1202  								"juju-status": M{
  1203  									"current": "idle",
  1204  									"since":   "01 Apr 15 01:23+10:00",
  1205  								},
  1206  								"public-address": "10.0.1.1",
  1207  							},
  1208  						},
  1209  					}),
  1210  				},
  1211  				"storage": M{},
  1212  				"controller": M{
  1213  					"timestamp": "15:04:05+07:00",
  1214  				},
  1215  			},
  1216  		},
  1217  		scopedExpect{
  1218  			what:  "scope status on unit pattern",
  1219  			scope: []string{"e*posed-application/*"},
  1220  			output: M{
  1221  				"model": model,
  1222  				"machines": M{
  1223  					"2": machine2,
  1224  				},
  1225  				"applications": M{
  1226  					"exposed-application": dummyCharm(M{
  1227  						"exposed": true,
  1228  						"application-status": M{
  1229  							"current": "error",
  1230  							"message": "You Require More Vespene Gas",
  1231  							"since":   "01 Apr 15 01:23+10:00",
  1232  						},
  1233  						"units": M{
  1234  							"exposed-application/0": M{
  1235  								"machine": "2",
  1236  								"workload-status": M{
  1237  									"current": "error",
  1238  									"message": "You Require More Vespene Gas",
  1239  									"since":   "01 Apr 15 01:23+10:00",
  1240  								},
  1241  								"juju-status": M{
  1242  									"current": "idle",
  1243  									"since":   "01 Apr 15 01:23+10:00",
  1244  								},
  1245  								"open-ports": L{
  1246  									"2/tcp", "3/tcp", "2/udp", "10/udp",
  1247  								},
  1248  								"public-address": "10.0.2.1",
  1249  							},
  1250  						},
  1251  					}),
  1252  				},
  1253  				"storage": M{},
  1254  				"controller": M{
  1255  					"timestamp": "15:04:05+07:00",
  1256  				},
  1257  			},
  1258  		},
  1259  		scopedExpect{
  1260  			what:  "scope status on combination of application and unit patterns",
  1261  			scope: []string{"exposed-application", "dummy-application", "e*posed-application/*", "dummy-application/*"},
  1262  			output: M{
  1263  				"model": model,
  1264  				"machines": M{
  1265  					"1": machine1,
  1266  					"2": machine2,
  1267  				},
  1268  				"applications": M{
  1269  					"dummy-application": dummyCharm(M{
  1270  						"application-status": M{
  1271  							"current": "terminated",
  1272  							"since":   "01 Apr 15 01:23+10:00",
  1273  						},
  1274  						"units": M{
  1275  							"dummy-application/0": M{
  1276  								"machine": "1",
  1277  								"workload-status": M{
  1278  									"current": "terminated",
  1279  									"since":   "01 Apr 15 01:23+10:00",
  1280  								},
  1281  								"juju-status": M{
  1282  									"current": "idle",
  1283  									"since":   "01 Apr 15 01:23+10:00",
  1284  								},
  1285  								"public-address": "10.0.1.1",
  1286  							},
  1287  						},
  1288  					}),
  1289  					"exposed-application": dummyCharm(M{
  1290  						"exposed": true,
  1291  						"application-status": M{
  1292  							"current": "error",
  1293  							"message": "You Require More Vespene Gas",
  1294  							"since":   "01 Apr 15 01:23+10:00",
  1295  						},
  1296  						"units": M{
  1297  							"exposed-application/0": M{
  1298  								"machine": "2",
  1299  								"workload-status": M{
  1300  									"current": "error",
  1301  									"message": "You Require More Vespene Gas",
  1302  									"since":   "01 Apr 15 01:23+10:00",
  1303  								},
  1304  								"juju-status": M{
  1305  									"current": "idle",
  1306  									"since":   "01 Apr 15 01:23+10:00",
  1307  								},
  1308  								"open-ports": L{
  1309  									"2/tcp", "3/tcp", "2/udp", "10/udp",
  1310  								},
  1311  								"public-address": "10.0.2.1",
  1312  							},
  1313  						},
  1314  					}),
  1315  				},
  1316  				"storage": M{},
  1317  				"controller": M{
  1318  					"timestamp": "15:04:05+07:00",
  1319  				},
  1320  			},
  1321  		},
  1322  	),
  1323  	test( // 5
  1324  		"a unit with a hook relation error",
  1325  		addMachine{machineId: "0", job: state.JobManageModel},
  1326  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  1327  		startAliveMachine{"0", ""},
  1328  		setMachineStatus{"0", status.Started, ""},
  1329  
  1330  		addMachine{machineId: "1", job: state.JobHostUnits},
  1331  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  1332  		startAliveMachine{"1", ""},
  1333  		setMachineStatus{"1", status.Started, ""},
  1334  
  1335  		addCharm{"wordpress"},
  1336  		addApplication{name: "wordpress", charm: "wordpress"},
  1337  		addAliveUnit{"wordpress", "1"},
  1338  
  1339  		addCharm{"mysql"},
  1340  		addApplication{name: "mysql", charm: "mysql"},
  1341  		addAliveUnit{"mysql", "1"},
  1342  
  1343  		relateApplications{"wordpress", "mysql", ""},
  1344  
  1345  		setAgentStatus{"wordpress/0", status.Error,
  1346  			"hook failed: some-relation-changed",
  1347  			map[string]interface{}{"relation-id": 0}},
  1348  
  1349  		expect{
  1350  			what: "a unit with a hook relation error",
  1351  			output: M{
  1352  				"model": model,
  1353  				"machines": M{
  1354  					"0": machine0,
  1355  					"1": machine1,
  1356  				},
  1357  				"applications": M{
  1358  					"wordpress": wordpressCharm(M{
  1359  						"relations": M{
  1360  							"db": L{"mysql"},
  1361  						},
  1362  						"application-status": M{
  1363  							"current": "error",
  1364  							"message": "hook failed: some-relation-changed",
  1365  							"since":   "01 Apr 15 01:23+10:00",
  1366  						},
  1367  						"units": M{
  1368  							"wordpress/0": M{
  1369  								"machine": "1",
  1370  								"workload-status": M{
  1371  									"current": "error",
  1372  									"message": "hook failed: some-relation-changed for mysql:server",
  1373  									"since":   "01 Apr 15 01:23+10:00",
  1374  								},
  1375  								"juju-status": M{
  1376  									"current": "idle",
  1377  									"since":   "01 Apr 15 01:23+10:00",
  1378  								},
  1379  								"public-address": "10.0.1.1",
  1380  							},
  1381  						},
  1382  						"endpoint-bindings": M{
  1383  							"logging-dir":     "",
  1384  							"monitoring-port": "",
  1385  							"url":             "",
  1386  							"admin-api":       "",
  1387  							"cache":           "",
  1388  							"db":              "",
  1389  							"db-client":       "",
  1390  							"foo-bar":         "",
  1391  						},
  1392  					}),
  1393  					"mysql": mysqlCharm(M{
  1394  						"relations": M{
  1395  							"server": L{"wordpress"},
  1396  						},
  1397  						"application-status": M{
  1398  							"current": "waiting",
  1399  							"message": "waiting for machine",
  1400  							"since":   "01 Apr 15 01:23+10:00",
  1401  						},
  1402  						"units": M{
  1403  							"mysql/0": M{
  1404  								"machine": "1",
  1405  								"workload-status": M{
  1406  									"current": "waiting",
  1407  									"message": "waiting for machine",
  1408  									"since":   "01 Apr 15 01:23+10:00",
  1409  								},
  1410  								"juju-status": M{
  1411  									"current": "allocating",
  1412  									"since":   "01 Apr 15 01:23+10:00",
  1413  								},
  1414  								"public-address": "10.0.1.1",
  1415  							},
  1416  						},
  1417  						"endpoint-bindings": M{
  1418  							"server":         "",
  1419  							"server-admin":   "",
  1420  							"metrics-client": "",
  1421  						},
  1422  					}),
  1423  				},
  1424  				"storage": M{},
  1425  				"controller": M{
  1426  					"timestamp": "15:04:05+07:00",
  1427  				},
  1428  			},
  1429  		},
  1430  	),
  1431  	test( // 6
  1432  		"a unit with a hook relation error when the agent is down",
  1433  		addMachine{machineId: "0", job: state.JobManageModel},
  1434  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  1435  		startAliveMachine{"0", ""},
  1436  		setMachineStatus{"0", status.Started, ""},
  1437  
  1438  		addMachine{machineId: "1", job: state.JobHostUnits},
  1439  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  1440  		startAliveMachine{"1", ""},
  1441  		setMachineStatus{"1", status.Started, ""},
  1442  
  1443  		addCharm{"wordpress"},
  1444  		addApplication{name: "wordpress", charm: "wordpress"},
  1445  		addAliveUnit{"wordpress", "1"},
  1446  
  1447  		addCharm{"mysql"},
  1448  		addApplication{name: "mysql", charm: "mysql"},
  1449  		addAliveUnit{"mysql", "1"},
  1450  
  1451  		relateApplications{"wordpress", "mysql", ""},
  1452  
  1453  		setAgentStatus{"wordpress/0", status.Error,
  1454  			"hook failed: some-relation-changed",
  1455  			map[string]interface{}{"relation-id": 0}},
  1456  
  1457  		expect{
  1458  			what: "a unit with a hook relation error when the agent is down",
  1459  			output: M{
  1460  				"model": model,
  1461  				"machines": M{
  1462  					"0": machine0,
  1463  					"1": machine1,
  1464  				},
  1465  				"applications": M{
  1466  					"wordpress": wordpressCharm(M{
  1467  						"relations": M{
  1468  							"db": L{"mysql"},
  1469  						},
  1470  						"application-status": M{
  1471  							"current": "error",
  1472  							"message": "hook failed: some-relation-changed",
  1473  							"since":   "01 Apr 15 01:23+10:00",
  1474  						},
  1475  						"units": M{
  1476  							"wordpress/0": M{
  1477  								"machine": "1",
  1478  								"workload-status": M{
  1479  									"current": "error",
  1480  									"message": "hook failed: some-relation-changed for mysql:server",
  1481  									"since":   "01 Apr 15 01:23+10:00",
  1482  								},
  1483  								"juju-status": M{
  1484  									"current": "idle",
  1485  									"since":   "01 Apr 15 01:23+10:00",
  1486  								},
  1487  								"public-address": "10.0.1.1",
  1488  							},
  1489  						},
  1490  						"endpoint-bindings": M{
  1491  							"admin-api":       "",
  1492  							"cache":           "",
  1493  							"db":              "",
  1494  							"db-client":       "",
  1495  							"foo-bar":         "",
  1496  							"logging-dir":     "",
  1497  							"monitoring-port": "",
  1498  							"url":             "",
  1499  						},
  1500  					}),
  1501  					"mysql": mysqlCharm(M{
  1502  						"relations": M{
  1503  							"server": L{"wordpress"},
  1504  						},
  1505  						"application-status": M{
  1506  							"current": "waiting",
  1507  							"message": "waiting for machine",
  1508  							"since":   "01 Apr 15 01:23+10:00",
  1509  						},
  1510  						"units": M{
  1511  							"mysql/0": M{
  1512  								"machine": "1",
  1513  								"workload-status": M{
  1514  									"current": "waiting",
  1515  									"message": "waiting for machine",
  1516  									"since":   "01 Apr 15 01:23+10:00",
  1517  								},
  1518  								"juju-status": M{
  1519  									"current": "allocating",
  1520  									"since":   "01 Apr 15 01:23+10:00",
  1521  								},
  1522  								"public-address": "10.0.1.1",
  1523  							},
  1524  						},
  1525  						"endpoint-bindings": M{
  1526  							"server":         "",
  1527  							"server-admin":   "",
  1528  							"metrics-client": "",
  1529  						},
  1530  					}),
  1531  				},
  1532  				"storage": M{},
  1533  				"controller": M{
  1534  					"timestamp": "15:04:05+07:00",
  1535  				},
  1536  			},
  1537  		},
  1538  	),
  1539  	test( // 7
  1540  		"add a dying application",
  1541  		addCharm{"dummy"},
  1542  		addApplication{name: "dummy-application", charm: "dummy"},
  1543  		addMachine{machineId: "0", job: state.JobHostUnits},
  1544  		addAliveUnit{"dummy-application", "0"},
  1545  		ensureDyingApplication{"dummy-application"},
  1546  		expect{
  1547  			what: "application shows life==dying",
  1548  			output: M{
  1549  				"model": model,
  1550  				"machines": M{
  1551  					"0": M{
  1552  						"juju-status": M{
  1553  							"current": "pending",
  1554  							"since":   "01 Apr 15 01:23+10:00",
  1555  						},
  1556  						"instance-id": "pending",
  1557  						"machine-status": M{
  1558  							"current": "pending",
  1559  							"since":   "01 Apr 15 01:23+10:00",
  1560  						},
  1561  
  1562  						"series": "quantal",
  1563  					},
  1564  				},
  1565  				"applications": M{
  1566  					"dummy-application": dummyCharm(M{
  1567  						"life": "dying",
  1568  						"application-status": M{
  1569  							"current": "waiting",
  1570  							"message": "waiting for machine",
  1571  							"since":   "01 Apr 15 01:23+10:00",
  1572  						},
  1573  						"units": M{
  1574  							"dummy-application/0": M{
  1575  								"machine": "0",
  1576  								"workload-status": M{
  1577  									"current": "waiting",
  1578  									"message": "waiting for machine",
  1579  									"since":   "01 Apr 15 01:23+10:00",
  1580  								},
  1581  								"juju-status": M{
  1582  									"current": "allocating",
  1583  									"since":   "01 Apr 15 01:23+10:00",
  1584  								},
  1585  							},
  1586  						},
  1587  					}),
  1588  				},
  1589  				"storage": M{},
  1590  				"controller": M{
  1591  					"timestamp": "15:04:05+07:00",
  1592  				},
  1593  			},
  1594  		},
  1595  	),
  1596  	test( // 8
  1597  		"a unit where the agent is down shows as lost",
  1598  		addCharm{"dummy"},
  1599  		addApplication{name: "dummy-application", charm: "dummy"},
  1600  		addMachine{machineId: "0", job: state.JobHostUnits},
  1601  		startAliveMachine{"0", ""},
  1602  		setMachineStatus{"0", status.Started, ""},
  1603  		addUnit{"dummy-application", "0"},
  1604  		setAgentStatus{"dummy-application/0", status.Idle, "", nil},
  1605  		setUnitStatus{"dummy-application/0", status.Active, "", nil},
  1606  		expect{
  1607  			what: "unit shows that agent is lost",
  1608  			output: M{
  1609  				"model": model,
  1610  				"machines": M{
  1611  					"0": M{
  1612  						"juju-status": M{
  1613  							"current": "started",
  1614  							"since":   "01 Apr 15 01:23+10:00",
  1615  						},
  1616  						"instance-id": "controller-0",
  1617  						"machine-status": M{
  1618  							"current": "pending",
  1619  							"since":   "01 Apr 15 01:23+10:00",
  1620  						},
  1621  
  1622  						"series":   "quantal",
  1623  						"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
  1624  					},
  1625  				},
  1626  				"applications": M{
  1627  					"dummy-application": dummyCharm(M{
  1628  						"application-status": M{
  1629  							"current": "active",
  1630  							"since":   "01 Apr 15 01:23+10:00",
  1631  						},
  1632  						"units": M{
  1633  							"dummy-application/0": M{
  1634  								"machine": "0",
  1635  								"workload-status": M{
  1636  									"current": "unknown",
  1637  									"message": "agent lost, see 'juju show-status-log dummy-application/0'",
  1638  									"since":   "01 Apr 15 01:23+10:00",
  1639  								},
  1640  								"juju-status": M{
  1641  									"current": "lost",
  1642  									"message": "agent is not communicating with the server",
  1643  									"since":   "01 Apr 15 01:23+10:00",
  1644  								},
  1645  							},
  1646  						},
  1647  					}),
  1648  				},
  1649  				"storage": M{},
  1650  				"controller": M{
  1651  					"timestamp": "15:04:05+07:00",
  1652  				},
  1653  			},
  1654  		},
  1655  	),
  1656  
  1657  	// Relation tests
  1658  	test( // 9
  1659  		"complex scenario with multiple related applications",
  1660  		addMachine{machineId: "0", job: state.JobManageModel},
  1661  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  1662  		startAliveMachine{"0", ""},
  1663  		setMachineStatus{"0", status.Started, ""},
  1664  		addCharm{"wordpress"},
  1665  		addCharm{"mysql"},
  1666  		addCharm{"varnish"},
  1667  
  1668  		addApplication{name: "project", charm: "wordpress"},
  1669  		setApplicationExposed{"project", true},
  1670  		addMachine{machineId: "1", job: state.JobHostUnits},
  1671  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  1672  		startAliveMachine{"1", ""},
  1673  		setMachineStatus{"1", status.Started, ""},
  1674  		addAliveUnit{"project", "1"},
  1675  		setAgentStatus{"project/0", status.Idle, "", nil},
  1676  		setUnitStatus{"project/0", status.Active, "", nil},
  1677  
  1678  		addApplication{name: "mysql", charm: "mysql"},
  1679  		setApplicationExposed{"mysql", true},
  1680  		addMachine{machineId: "2", job: state.JobHostUnits},
  1681  		setAddresses{"2", network.NewAddresses("10.0.2.1")},
  1682  		startAliveMachine{"2", ""},
  1683  		setMachineStatus{"2", status.Started, ""},
  1684  		addAliveUnit{"mysql", "2"},
  1685  		setAgentStatus{"mysql/0", status.Idle, "", nil},
  1686  		setUnitStatus{"mysql/0", status.Active, "", nil},
  1687  
  1688  		addApplication{name: "varnish", charm: "varnish"},
  1689  		setApplicationExposed{"varnish", true},
  1690  		addMachine{machineId: "3", job: state.JobHostUnits},
  1691  		setAddresses{"3", network.NewAddresses("10.0.3.1")},
  1692  		startAliveMachine{"3", ""},
  1693  		setMachineStatus{"3", status.Started, ""},
  1694  		setMachineInstanceStatus{"3", status.Started, "I am number three"},
  1695  		addAliveUnit{"varnish", "3"},
  1696  
  1697  		addApplication{name: "private", charm: "wordpress"},
  1698  		setApplicationExposed{"private", true},
  1699  		addMachine{machineId: "4", job: state.JobHostUnits},
  1700  		setAddresses{"4", network.NewAddresses("10.0.4.1")},
  1701  		startAliveMachine{"4", ""},
  1702  		setMachineStatus{"4", status.Started, ""},
  1703  		addAliveUnit{"private", "4"},
  1704  
  1705  		relateApplications{"project", "mysql", ""},
  1706  		relateApplications{"project", "varnish", ""},
  1707  		relateApplications{"private", "mysql", ""},
  1708  
  1709  		expect{
  1710  			what: "multiples applications with relations between some of them",
  1711  			output: M{
  1712  				"model": model,
  1713  				"machines": M{
  1714  					"0": machine0,
  1715  					"1": machine1,
  1716  					"2": machine2,
  1717  					"3": machine3,
  1718  					"4": machine4,
  1719  				},
  1720  				"applications": M{
  1721  					"project": wordpressCharm(M{
  1722  						"exposed": true,
  1723  						"application-status": M{
  1724  							"current": "active",
  1725  							"since":   "01 Apr 15 01:23+10:00",
  1726  						},
  1727  						"units": M{
  1728  							"project/0": M{
  1729  								"machine": "1",
  1730  								"workload-status": M{
  1731  									"current": "active",
  1732  									"since":   "01 Apr 15 01:23+10:00",
  1733  								},
  1734  								"juju-status": M{
  1735  									"current": "idle",
  1736  									"since":   "01 Apr 15 01:23+10:00",
  1737  								},
  1738  								"public-address": "10.0.1.1",
  1739  							},
  1740  						},
  1741  						"endpoint-bindings": M{
  1742  							"db":              "",
  1743  							"db-client":       "",
  1744  							"foo-bar":         "",
  1745  							"logging-dir":     "",
  1746  							"monitoring-port": "",
  1747  							"url":             "",
  1748  							"admin-api":       "",
  1749  							"cache":           "",
  1750  						},
  1751  						"relations": M{
  1752  							"db":    L{"mysql"},
  1753  							"cache": L{"varnish"},
  1754  						},
  1755  					}),
  1756  					"mysql": mysqlCharm(M{
  1757  						"exposed": true,
  1758  						"application-status": M{
  1759  							"current": "active",
  1760  							"since":   "01 Apr 15 01:23+10:00",
  1761  						},
  1762  						"units": M{
  1763  							"mysql/0": M{
  1764  								"machine": "2",
  1765  								"workload-status": M{
  1766  									"current": "active",
  1767  									"since":   "01 Apr 15 01:23+10:00",
  1768  								},
  1769  								"juju-status": M{
  1770  									"current": "idle",
  1771  									"since":   "01 Apr 15 01:23+10:00",
  1772  								},
  1773  								"public-address": "10.0.2.1",
  1774  							},
  1775  						},
  1776  						"endpoint-bindings": M{
  1777  							"server":         "",
  1778  							"server-admin":   "",
  1779  							"metrics-client": "",
  1780  						},
  1781  						"relations": M{
  1782  							"server": L{"private", "project"},
  1783  						},
  1784  					}),
  1785  					"varnish": M{
  1786  						"charm":        "cs:quantal/varnish-1",
  1787  						"charm-origin": "jujucharms",
  1788  						"charm-name":   "varnish",
  1789  						"charm-rev":    1,
  1790  						"series":       "quantal",
  1791  						"os":           "ubuntu",
  1792  						"exposed":      true,
  1793  						"application-status": M{
  1794  							"current": "waiting",
  1795  							"message": "waiting for machine",
  1796  							"since":   "01 Apr 15 01:23+10:00",
  1797  						},
  1798  						"units": M{
  1799  							"varnish/0": M{
  1800  								"machine": "3",
  1801  								"workload-status": M{
  1802  									"current": "waiting",
  1803  									"message": "waiting for machine",
  1804  									"since":   "01 Apr 15 01:23+10:00",
  1805  								},
  1806  								"juju-status": M{
  1807  									"current": "allocating",
  1808  									"since":   "01 Apr 15 01:23+10:00",
  1809  								},
  1810  								"public-address": "10.0.3.1",
  1811  							},
  1812  						},
  1813  						"endpoint-bindings": M{
  1814  							"webcache": "",
  1815  						},
  1816  						"relations": M{
  1817  							"webcache": L{"project"},
  1818  						},
  1819  					},
  1820  					"private": wordpressCharm(M{
  1821  						"exposed": true,
  1822  						"application-status": M{
  1823  							"current": "waiting",
  1824  							"message": "waiting for machine",
  1825  							"since":   "01 Apr 15 01:23+10:00",
  1826  						},
  1827  						"units": M{
  1828  							"private/0": M{
  1829  								"machine": "4",
  1830  								"workload-status": M{
  1831  									"current": "waiting",
  1832  									"message": "waiting for machine",
  1833  									"since":   "01 Apr 15 01:23+10:00",
  1834  								},
  1835  								"juju-status": M{
  1836  									"current": "allocating",
  1837  									"since":   "01 Apr 15 01:23+10:00",
  1838  								},
  1839  								"public-address": "10.0.4.1",
  1840  							},
  1841  						},
  1842  						"endpoint-bindings": M{
  1843  							"logging-dir":     "",
  1844  							"monitoring-port": "",
  1845  							"url":             "",
  1846  							"admin-api":       "",
  1847  							"cache":           "",
  1848  							"db":              "",
  1849  							"db-client":       "",
  1850  							"foo-bar":         "",
  1851  						},
  1852  						"relations": M{
  1853  							"db": L{"mysql"},
  1854  						},
  1855  					}),
  1856  				},
  1857  				"storage": M{},
  1858  				"controller": M{
  1859  					"timestamp": "15:04:05+07:00",
  1860  				},
  1861  			},
  1862  		},
  1863  	),
  1864  	test( // 10
  1865  		"simple peer scenario with leader",
  1866  		addMachine{machineId: "0", job: state.JobManageModel},
  1867  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  1868  		startAliveMachine{"0", ""},
  1869  		setMachineStatus{"0", status.Started, ""},
  1870  		addCharm{"riak"},
  1871  		addCharm{"wordpress"},
  1872  
  1873  		addApplication{name: "riak", charm: "riak"},
  1874  		setApplicationExposed{"riak", true},
  1875  		addMachine{machineId: "1", job: state.JobHostUnits},
  1876  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  1877  		startAliveMachine{"1", ""},
  1878  		setMachineStatus{"1", status.Started, ""},
  1879  		addAliveUnit{"riak", "1"},
  1880  		setAgentStatus{"riak/0", status.Idle, "", nil},
  1881  		setUnitStatus{"riak/0", status.Active, "", nil},
  1882  		addMachine{machineId: "2", job: state.JobHostUnits},
  1883  		setAddresses{"2", network.NewAddresses("10.0.2.1")},
  1884  		startAliveMachine{"2", ""},
  1885  		setMachineStatus{"2", status.Started, ""},
  1886  		addAliveUnit{"riak", "2"},
  1887  		setAgentStatus{"riak/1", status.Idle, "", nil},
  1888  		setUnitStatus{"riak/1", status.Active, "", nil},
  1889  		addMachine{machineId: "3", job: state.JobHostUnits},
  1890  		setAddresses{"3", network.NewAddresses("10.0.3.1")},
  1891  		startAliveMachine{"3", ""},
  1892  		setMachineStatus{"3", status.Started, ""},
  1893  		setMachineInstanceStatus{"3", status.Started, "I am number three"},
  1894  		addAliveUnit{"riak", "3"},
  1895  		setAgentStatus{"riak/2", status.Idle, "", nil},
  1896  		setUnitStatus{"riak/2", status.Active, "", nil},
  1897  		setUnitAsLeader{"riak/1"},
  1898  
  1899  		expect{
  1900  			what: "multiples related peer units",
  1901  			output: M{
  1902  				"model": model,
  1903  				"machines": M{
  1904  					"0": machine0,
  1905  					"1": machine1,
  1906  					"2": machine2,
  1907  					"3": machine3,
  1908  				},
  1909  				"applications": M{
  1910  					"riak": M{
  1911  						"charm":        "cs:quantal/riak-7",
  1912  						"charm-origin": "jujucharms",
  1913  						"charm-name":   "riak",
  1914  						"charm-rev":    7,
  1915  						"series":       "quantal",
  1916  						"os":           "ubuntu",
  1917  						"exposed":      true,
  1918  						"application-status": M{
  1919  							"current": "active",
  1920  							"since":   "01 Apr 15 01:23+10:00",
  1921  						},
  1922  						"units": M{
  1923  							"riak/0": M{
  1924  								"machine": "1",
  1925  								"workload-status": M{
  1926  									"current": "active",
  1927  									"since":   "01 Apr 15 01:23+10:00",
  1928  								},
  1929  								"juju-status": M{
  1930  									"current": "idle",
  1931  									"since":   "01 Apr 15 01:23+10:00",
  1932  								},
  1933  								"public-address": "10.0.1.1",
  1934  							},
  1935  							"riak/1": M{
  1936  								"machine": "2",
  1937  								"workload-status": M{
  1938  									"current": "active",
  1939  									"since":   "01 Apr 15 01:23+10:00",
  1940  								},
  1941  								"juju-status": M{
  1942  									"current": "idle",
  1943  									"since":   "01 Apr 15 01:23+10:00",
  1944  								},
  1945  								"public-address": "10.0.2.1",
  1946  								"leader":         true,
  1947  							},
  1948  							"riak/2": M{
  1949  								"machine": "3",
  1950  								"workload-status": M{
  1951  									"current": "active",
  1952  									"since":   "01 Apr 15 01:23+10:00",
  1953  								},
  1954  								"juju-status": M{
  1955  									"current": "idle",
  1956  									"since":   "01 Apr 15 01:23+10:00",
  1957  								},
  1958  								"public-address": "10.0.3.1",
  1959  							},
  1960  						},
  1961  						"endpoint-bindings": M{
  1962  							"admin":    "",
  1963  							"endpoint": "",
  1964  							"ring":     "",
  1965  						},
  1966  						"relations": M{
  1967  							"ring": L{"riak"},
  1968  						},
  1969  					},
  1970  				},
  1971  				"storage": M{},
  1972  				"controller": M{
  1973  					"timestamp": "15:04:05+07:00",
  1974  				},
  1975  			},
  1976  		},
  1977  	),
  1978  
  1979  	// Subordinate tests
  1980  	test( // 11
  1981  		"one application with one subordinate application and leader",
  1982  		addMachine{machineId: "0", job: state.JobManageModel},
  1983  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  1984  		startAliveMachine{"0", ""},
  1985  		setMachineStatus{"0", status.Started, ""},
  1986  		addCharm{"wordpress"},
  1987  		addCharm{"mysql"},
  1988  		addCharm{"logging"},
  1989  
  1990  		addApplication{name: "wordpress", charm: "wordpress"},
  1991  		setApplicationExposed{"wordpress", true},
  1992  		addMachine{machineId: "1", job: state.JobHostUnits},
  1993  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  1994  		startAliveMachine{"1", ""},
  1995  		setMachineStatus{"1", status.Started, ""},
  1996  		addAliveUnit{"wordpress", "1"},
  1997  		setAgentStatus{"wordpress/0", status.Idle, "", nil},
  1998  		setUnitStatus{"wordpress/0", status.Active, "", nil},
  1999  
  2000  		addApplication{name: "mysql", charm: "mysql"},
  2001  		setApplicationExposed{"mysql", true},
  2002  		addMachine{machineId: "2", job: state.JobHostUnits},
  2003  		setAddresses{"2", network.NewAddresses("10.0.2.1")},
  2004  		startAliveMachine{"2", ""},
  2005  		setMachineStatus{"2", status.Started, ""},
  2006  		addAliveUnit{"mysql", "2"},
  2007  		setAgentStatus{"mysql/0", status.Idle, "", nil},
  2008  		setUnitStatus{"mysql/0", status.Active, "", nil},
  2009  
  2010  		addApplication{name: "logging", charm: "logging"},
  2011  		setApplicationExposed{"logging", true},
  2012  
  2013  		relateApplications{"wordpress", "mysql", ""},
  2014  		relateApplications{"wordpress", "logging", ""},
  2015  		relateApplications{"mysql", "logging", ""},
  2016  
  2017  		addSubordinate{"wordpress/0", "logging"},
  2018  		addSubordinate{"mysql/0", "logging"},
  2019  
  2020  		setUnitsAlive{"logging"},
  2021  		setAgentStatus{"logging/0", status.Idle, "", nil},
  2022  		setUnitStatus{"logging/0", status.Active, "", nil},
  2023  		setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil},
  2024  
  2025  		setUnitAsLeader{"mysql/0"},
  2026  		setUnitAsLeader{"logging/1"},
  2027  		setUnitAsLeader{"wordpress/0"},
  2028  
  2029  		expect{
  2030  			what: "multiples related peer units",
  2031  			output: M{
  2032  				"model": model,
  2033  				"machines": M{
  2034  					"0": machine0,
  2035  					"1": machine1,
  2036  					"2": machine2,
  2037  				},
  2038  				"applications": M{
  2039  					"wordpress": wordpressCharm(M{
  2040  						"exposed": true,
  2041  						"application-status": M{
  2042  							"current": "active",
  2043  							"since":   "01 Apr 15 01:23+10:00",
  2044  						},
  2045  						"units": M{
  2046  							"wordpress/0": M{
  2047  								"machine": "1",
  2048  								"workload-status": M{
  2049  									"current": "active",
  2050  									"since":   "01 Apr 15 01:23+10:00",
  2051  								},
  2052  								"juju-status": M{
  2053  									"current": "idle",
  2054  									"since":   "01 Apr 15 01:23+10:00",
  2055  								},
  2056  								"subordinates": M{
  2057  									"logging/0": M{
  2058  										"workload-status": M{
  2059  											"current": "active",
  2060  											"since":   "01 Apr 15 01:23+10:00",
  2061  										},
  2062  										"juju-status": M{
  2063  											"current": "idle",
  2064  											"since":   "01 Apr 15 01:23+10:00",
  2065  										},
  2066  										"public-address": "10.0.1.1",
  2067  									},
  2068  								},
  2069  								"public-address": "10.0.1.1",
  2070  								"leader":         true,
  2071  							},
  2072  						},
  2073  						"endpoint-bindings": M{
  2074  							"monitoring-port": "",
  2075  							"url":             "",
  2076  							"admin-api":       "",
  2077  							"cache":           "",
  2078  							"db":              "",
  2079  							"db-client":       "",
  2080  							"foo-bar":         "",
  2081  							"logging-dir":     "",
  2082  						},
  2083  						"relations": M{
  2084  							"db":          L{"mysql"},
  2085  							"logging-dir": L{"logging"},
  2086  						},
  2087  					}),
  2088  					"mysql": mysqlCharm(M{
  2089  						"exposed": true,
  2090  						"application-status": M{
  2091  							"current": "active",
  2092  							"since":   "01 Apr 15 01:23+10:00",
  2093  						},
  2094  						"units": M{
  2095  							"mysql/0": M{
  2096  								"machine": "2",
  2097  								"workload-status": M{
  2098  									"current": "active",
  2099  									"since":   "01 Apr 15 01:23+10:00",
  2100  								},
  2101  								"juju-status": M{
  2102  									"current": "idle",
  2103  									"since":   "01 Apr 15 01:23+10:00",
  2104  								},
  2105  								"subordinates": M{
  2106  									"logging/1": M{
  2107  										"workload-status": M{
  2108  											"current": "error",
  2109  											"message": "somehow lost in all those logs",
  2110  											"since":   "01 Apr 15 01:23+10:00",
  2111  										},
  2112  										"juju-status": M{
  2113  											"current": "idle",
  2114  											"since":   "01 Apr 15 01:23+10:00",
  2115  										},
  2116  										"public-address": "10.0.2.1",
  2117  										"leader":         true,
  2118  									},
  2119  								},
  2120  								"public-address": "10.0.2.1",
  2121  								"leader":         true,
  2122  							},
  2123  						},
  2124  						"endpoint-bindings": M{
  2125  							"server":         "",
  2126  							"server-admin":   "",
  2127  							"metrics-client": "",
  2128  						},
  2129  						"relations": M{
  2130  							"server":    L{"wordpress"},
  2131  							"juju-info": L{"logging"},
  2132  						},
  2133  					}),
  2134  					"logging": loggingCharm,
  2135  				},
  2136  				"storage": M{},
  2137  				"controller": M{
  2138  					"timestamp": "15:04:05+07:00",
  2139  				},
  2140  			},
  2141  		},
  2142  
  2143  		// scoped on 'logging'
  2144  		scopedExpect{
  2145  			what:  "subordinates scoped on logging",
  2146  			scope: []string{"logging"},
  2147  			output: M{
  2148  				"model": model,
  2149  				"machines": M{
  2150  					"1": machine1,
  2151  					"2": machine2,
  2152  				},
  2153  				"applications": M{
  2154  					"wordpress": wordpressCharm(M{
  2155  						"exposed": true,
  2156  						"application-status": M{
  2157  							"current": "active",
  2158  							"since":   "01 Apr 15 01:23+10:00",
  2159  						},
  2160  						"units": M{
  2161  							"wordpress/0": M{
  2162  								"machine": "1",
  2163  								"workload-status": M{
  2164  									"current": "active",
  2165  									"since":   "01 Apr 15 01:23+10:00",
  2166  								},
  2167  								"juju-status": M{
  2168  									"current": "idle",
  2169  									"since":   "01 Apr 15 01:23+10:00",
  2170  								},
  2171  								"subordinates": M{
  2172  									"logging/0": M{
  2173  										"workload-status": M{
  2174  											"current": "active",
  2175  											"since":   "01 Apr 15 01:23+10:00",
  2176  										},
  2177  										"juju-status": M{
  2178  											"current": "idle",
  2179  											"since":   "01 Apr 15 01:23+10:00",
  2180  										},
  2181  										"public-address": "10.0.1.1",
  2182  									},
  2183  								},
  2184  								"public-address": "10.0.1.1",
  2185  								"leader":         true,
  2186  							},
  2187  						},
  2188  						"endpoint-bindings": M{
  2189  							"monitoring-port": "",
  2190  							"url":             "",
  2191  							"admin-api":       "",
  2192  							"cache":           "",
  2193  							"db":              "",
  2194  							"db-client":       "",
  2195  							"foo-bar":         "",
  2196  							"logging-dir":     "",
  2197  						},
  2198  						"relations": M{
  2199  							"db":          L{"mysql"},
  2200  							"logging-dir": L{"logging"},
  2201  						},
  2202  					}),
  2203  					"mysql": mysqlCharm(M{
  2204  						"exposed": true,
  2205  						"application-status": M{
  2206  							"current": "active",
  2207  							"since":   "01 Apr 15 01:23+10:00",
  2208  						},
  2209  						"units": M{
  2210  							"mysql/0": M{
  2211  								"machine": "2",
  2212  								"workload-status": M{
  2213  									"current": "active",
  2214  									"since":   "01 Apr 15 01:23+10:00",
  2215  								},
  2216  								"juju-status": M{
  2217  									"current": "idle",
  2218  									"since":   "01 Apr 15 01:23+10:00",
  2219  								},
  2220  								"subordinates": M{
  2221  									"logging/1": M{
  2222  										"workload-status": M{
  2223  											"current": "error",
  2224  											"message": "somehow lost in all those logs",
  2225  											"since":   "01 Apr 15 01:23+10:00",
  2226  										},
  2227  										"juju-status": M{
  2228  											"current": "idle",
  2229  											"since":   "01 Apr 15 01:23+10:00",
  2230  										},
  2231  										"public-address": "10.0.2.1",
  2232  										"leader":         true,
  2233  									},
  2234  								},
  2235  								"public-address": "10.0.2.1",
  2236  								"leader":         true,
  2237  							},
  2238  						},
  2239  						"endpoint-bindings": M{
  2240  							"server":         "",
  2241  							"server-admin":   "",
  2242  							"metrics-client": "",
  2243  						},
  2244  						"relations": M{
  2245  							"server":    L{"wordpress"},
  2246  							"juju-info": L{"logging"},
  2247  						},
  2248  					}),
  2249  					"logging": loggingCharm,
  2250  				},
  2251  				"storage": M{},
  2252  				"controller": M{
  2253  					"timestamp": "15:04:05+07:00",
  2254  				},
  2255  			},
  2256  		},
  2257  
  2258  		// scoped on wordpress/0
  2259  		scopedExpect{
  2260  			what:  "subordinates scoped on wordpress",
  2261  			scope: []string{"wordpress/0"},
  2262  			output: M{
  2263  				"model": model,
  2264  				"machines": M{
  2265  					"1": machine1,
  2266  				},
  2267  				"applications": M{
  2268  					"wordpress": wordpressCharm(M{
  2269  						"exposed": true,
  2270  						"application-status": M{
  2271  							"current": "active",
  2272  							"since":   "01 Apr 15 01:23+10:00",
  2273  						},
  2274  						"units": M{
  2275  							"wordpress/0": M{
  2276  								"machine": "1",
  2277  								"workload-status": M{
  2278  									"current": "active",
  2279  									"since":   "01 Apr 15 01:23+10:00",
  2280  								},
  2281  								"juju-status": M{
  2282  									"current": "idle",
  2283  									"since":   "01 Apr 15 01:23+10:00",
  2284  								},
  2285  								"subordinates": M{
  2286  									"logging/0": M{
  2287  										"workload-status": M{
  2288  											"current": "active",
  2289  											"since":   "01 Apr 15 01:23+10:00",
  2290  										},
  2291  										"juju-status": M{
  2292  											"current": "idle",
  2293  											"since":   "01 Apr 15 01:23+10:00",
  2294  										},
  2295  										"public-address": "10.0.1.1",
  2296  									},
  2297  								},
  2298  								"public-address": "10.0.1.1",
  2299  								"leader":         true,
  2300  							},
  2301  						},
  2302  						"endpoint-bindings": M{
  2303  							"admin-api":       "",
  2304  							"cache":           "",
  2305  							"db":              "",
  2306  							"db-client":       "",
  2307  							"foo-bar":         "",
  2308  							"logging-dir":     "",
  2309  							"monitoring-port": "",
  2310  							"url":             "",
  2311  						},
  2312  						"relations": M{
  2313  							"db":          L{"mysql"},
  2314  							"logging-dir": L{"logging"},
  2315  						},
  2316  					}),
  2317  					"logging": loggingCharm,
  2318  				},
  2319  				"storage": M{},
  2320  				"controller": M{
  2321  					"timestamp": "15:04:05+07:00",
  2322  				},
  2323  			},
  2324  		},
  2325  	),
  2326  	test( // 12
  2327  		"machines with containers",
  2328  		// step 0
  2329  		addMachine{machineId: "0", job: state.JobManageModel},
  2330  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  2331  		startAliveMachine{"0", ""},
  2332  		setMachineStatus{"0", status.Started, ""},
  2333  		addCharm{"mysql"},
  2334  		addApplication{name: "mysql", charm: "mysql"},
  2335  		setApplicationExposed{"mysql", true},
  2336  
  2337  		// step 7
  2338  		addMachine{machineId: "1", job: state.JobHostUnits},
  2339  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  2340  		startAliveMachine{"1", ""},
  2341  		setMachineStatus{"1", status.Started, ""},
  2342  		addAliveUnit{"mysql", "1"},
  2343  		setAgentStatus{"mysql/0", status.Idle, "", nil},
  2344  		setUnitStatus{"mysql/0", status.Active, "", nil},
  2345  
  2346  		// step 14: A container on machine 1.
  2347  		addContainer{"1", "1/lxd/0", state.JobHostUnits},
  2348  		setAddresses{"1/lxd/0", network.NewAddresses("10.0.2.1")},
  2349  		startAliveMachine{"1/lxd/0", ""},
  2350  		setMachineStatus{"1/lxd/0", status.Started, ""},
  2351  		addAliveUnit{"mysql", "1/lxd/0"},
  2352  		setAgentStatus{"mysql/1", status.Idle, "", nil},
  2353  		setUnitStatus{"mysql/1", status.Active, "", nil},
  2354  		addContainer{"1", "1/lxd/1", state.JobHostUnits},
  2355  
  2356  		// step 22: A nested container.
  2357  		addContainer{"1/lxd/0", "1/lxd/0/lxd/0", state.JobHostUnits},
  2358  		setAddresses{"1/lxd/0/lxd/0", network.NewAddresses("10.0.3.1")},
  2359  		startAliveMachine{"1/lxd/0/lxd/0", ""},
  2360  		setMachineStatus{"1/lxd/0/lxd/0", status.Started, ""},
  2361  
  2362  		expect{
  2363  			what: "machines with nested containers",
  2364  			output: M{
  2365  				"model": model,
  2366  				"machines": M{
  2367  					"0": machine0,
  2368  					"1": machine1WithContainers,
  2369  				},
  2370  				"applications": M{
  2371  					"mysql": mysqlCharm(M{
  2372  						"exposed": true,
  2373  						"application-status": M{
  2374  							"current": "active",
  2375  							"since":   "01 Apr 15 01:23+10:00",
  2376  						},
  2377  						"units": M{
  2378  							"mysql/0": M{
  2379  								"machine": "1",
  2380  								"workload-status": M{
  2381  									"current": "active",
  2382  									"since":   "01 Apr 15 01:23+10:00",
  2383  								},
  2384  								"juju-status": M{
  2385  									"current": "idle",
  2386  									"since":   "01 Apr 15 01:23+10:00",
  2387  								},
  2388  								"public-address": "10.0.1.1",
  2389  							},
  2390  							"mysql/1": M{
  2391  								"machine": "1/lxd/0",
  2392  								"workload-status": M{
  2393  									"current": "active",
  2394  									"since":   "01 Apr 15 01:23+10:00",
  2395  								},
  2396  								"juju-status": M{
  2397  									"current": "idle",
  2398  									"since":   "01 Apr 15 01:23+10:00",
  2399  								},
  2400  								"public-address": "10.0.2.1",
  2401  							},
  2402  						},
  2403  						"endpoint-bindings": M{
  2404  							"server":         "",
  2405  							"server-admin":   "",
  2406  							"metrics-client": "",
  2407  						},
  2408  					}),
  2409  				},
  2410  				"storage": M{},
  2411  				"controller": M{
  2412  					"timestamp": "15:04:05+07:00",
  2413  				},
  2414  			},
  2415  		},
  2416  
  2417  		// step 27: once again, with a scope on mysql/1
  2418  		scopedExpect{
  2419  			what:  "machines with nested containers 2",
  2420  			scope: []string{"mysql/1"},
  2421  			output: M{
  2422  				"model": model,
  2423  				"machines": M{
  2424  					"1": M{
  2425  						"juju-status": M{
  2426  							"current": "started",
  2427  							"since":   "01 Apr 15 01:23+10:00",
  2428  						},
  2429  						"containers": M{
  2430  							"1/lxd/0": M{
  2431  								"juju-status": M{
  2432  									"current": "started",
  2433  									"since":   "01 Apr 15 01:23+10:00",
  2434  								},
  2435  								"dns-name":     "10.0.2.1",
  2436  								"ip-addresses": []string{"10.0.2.1"},
  2437  								"instance-id":  "controller-2",
  2438  								"machine-status": M{
  2439  									"current": "pending",
  2440  									"since":   "01 Apr 15 01:23+10:00",
  2441  								},
  2442  
  2443  								"series": "quantal",
  2444  								"network-interfaces": M{
  2445  									"eth0": M{
  2446  										"ip-addresses": []string{"10.0.2.1"},
  2447  										"mac-address":  "aa:bb:cc:dd:ee:ff",
  2448  										"is-up":        true,
  2449  									},
  2450  								},
  2451  							},
  2452  						},
  2453  						"dns-name":     "10.0.1.1",
  2454  						"ip-addresses": []string{"10.0.1.1"},
  2455  						"instance-id":  "controller-1",
  2456  						"machine-status": M{
  2457  							"current": "pending",
  2458  							"since":   "01 Apr 15 01:23+10:00",
  2459  						},
  2460  
  2461  						"series": "quantal",
  2462  						"network-interfaces": M{
  2463  							"eth0": M{
  2464  								"ip-addresses": []string{"10.0.1.1"},
  2465  								"mac-address":  "aa:bb:cc:dd:ee:ff",
  2466  								"is-up":        true,
  2467  							},
  2468  						},
  2469  						"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
  2470  					},
  2471  				},
  2472  				"applications": M{
  2473  					"mysql": mysqlCharm(M{
  2474  						"exposed": true,
  2475  						"application-status": M{
  2476  							"current": "active",
  2477  							"since":   "01 Apr 15 01:23+10:00",
  2478  						},
  2479  						"units": M{
  2480  							"mysql/1": M{
  2481  								"machine": "1/lxd/0",
  2482  								"workload-status": M{
  2483  									"current": "active",
  2484  									"since":   "01 Apr 15 01:23+10:00",
  2485  								},
  2486  								"juju-status": M{
  2487  									"current": "idle",
  2488  									"since":   "01 Apr 15 01:23+10:00",
  2489  								},
  2490  								"public-address": "10.0.2.1",
  2491  							},
  2492  						},
  2493  						"endpoint-bindings": M{
  2494  							"server":         "",
  2495  							"server-admin":   "",
  2496  							"metrics-client": "",
  2497  						},
  2498  					}),
  2499  				},
  2500  				"storage": M{},
  2501  				"controller": M{
  2502  					"timestamp": "15:04:05+07:00",
  2503  				},
  2504  			},
  2505  		},
  2506  	),
  2507  	test( // 13
  2508  		"application with out of date charm",
  2509  		addMachine{machineId: "0", job: state.JobManageModel},
  2510  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  2511  		startAliveMachine{"0", ""},
  2512  		setMachineStatus{"0", status.Started, ""},
  2513  		addMachine{machineId: "1", job: state.JobHostUnits},
  2514  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  2515  		startAliveMachine{"1", ""},
  2516  		setMachineStatus{"1", status.Started, ""},
  2517  		addCharm{"mysql"},
  2518  		addApplication{name: "mysql", charm: "mysql"},
  2519  		setApplicationExposed{"mysql", true},
  2520  		addCharmPlaceholder{"mysql", 23},
  2521  		addAliveUnit{"mysql", "1"},
  2522  
  2523  		expect{
  2524  			what: "applications and units with correct charm status",
  2525  			output: M{
  2526  				"model": model,
  2527  				"machines": M{
  2528  					"0": machine0,
  2529  					"1": machine1,
  2530  				},
  2531  				"applications": M{
  2532  					"mysql": mysqlCharm(M{
  2533  						"can-upgrade-to": "cs:quantal/mysql-23",
  2534  						"exposed":        true,
  2535  						"application-status": M{
  2536  							"current": "waiting",
  2537  							"message": "waiting for machine",
  2538  							"since":   "01 Apr 15 01:23+10:00",
  2539  						},
  2540  						"units": M{
  2541  							"mysql/0": M{
  2542  								"machine": "1",
  2543  								"workload-status": M{
  2544  									"current": "waiting",
  2545  									"message": "waiting for machine",
  2546  									"since":   "01 Apr 15 01:23+10:00",
  2547  								},
  2548  								"juju-status": M{
  2549  									"current": "allocating",
  2550  									"since":   "01 Apr 15 01:23+10:00",
  2551  								},
  2552  								"public-address": "10.0.1.1",
  2553  							},
  2554  						},
  2555  						"endpoint-bindings": M{
  2556  							"server":         "",
  2557  							"server-admin":   "",
  2558  							"metrics-client": "",
  2559  						},
  2560  					}),
  2561  				},
  2562  				"storage": M{},
  2563  				"controller": M{
  2564  					"timestamp": "15:04:05+07:00",
  2565  				},
  2566  			},
  2567  		},
  2568  	),
  2569  	test( // 14
  2570  		"unit with out of date charm",
  2571  		addMachine{machineId: "0", job: state.JobManageModel},
  2572  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  2573  		startAliveMachine{"0", ""},
  2574  		setMachineStatus{"0", status.Started, ""},
  2575  		addMachine{machineId: "1", job: state.JobHostUnits},
  2576  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  2577  		startAliveMachine{"1", ""},
  2578  		setMachineStatus{"1", status.Started, ""},
  2579  		addCharm{"mysql"},
  2580  		addApplication{name: "mysql", charm: "mysql"},
  2581  		setApplicationExposed{"mysql", true},
  2582  		addAliveUnit{"mysql", "1"},
  2583  		setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  2584  		addCharmWithRevision{addCharm{"mysql"}, "local", 1},
  2585  		setApplicationCharm{"mysql", "local:quantal/mysql-1"},
  2586  
  2587  		expect{
  2588  			what: "applications and units with correct charm status",
  2589  			output: M{
  2590  				"model": model,
  2591  				"machines": M{
  2592  					"0": machine0,
  2593  					"1": machine1,
  2594  				},
  2595  				"applications": M{
  2596  					"mysql": mysqlCharm(M{
  2597  						"charm":        "local:quantal/mysql-1",
  2598  						"charm-origin": "local",
  2599  						"exposed":      true,
  2600  						"application-status": M{
  2601  							"current": "active",
  2602  							"since":   "01 Apr 15 01:23+10:00",
  2603  						},
  2604  						"units": M{
  2605  							"mysql/0": M{
  2606  								"machine": "1",
  2607  								"workload-status": M{
  2608  									"current": "active",
  2609  									"since":   "01 Apr 15 01:23+10:00",
  2610  								},
  2611  								"juju-status": M{
  2612  									"current": "idle",
  2613  									"since":   "01 Apr 15 01:23+10:00",
  2614  								},
  2615  								"upgrading-from": "cs:quantal/mysql-1",
  2616  								"public-address": "10.0.1.1",
  2617  							},
  2618  						},
  2619  						"endpoint-bindings": M{
  2620  							"server":         "",
  2621  							"server-admin":   "",
  2622  							"metrics-client": "",
  2623  						},
  2624  					}),
  2625  				},
  2626  				"storage": M{},
  2627  				"controller": M{
  2628  					"timestamp": "15:04:05+07:00",
  2629  				},
  2630  			},
  2631  		},
  2632  	),
  2633  	test( // 15
  2634  		"application and unit with out of date charms",
  2635  		addMachine{machineId: "0", job: state.JobManageModel},
  2636  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  2637  		startAliveMachine{"0", ""},
  2638  		setMachineStatus{"0", status.Started, ""},
  2639  		addMachine{machineId: "1", job: state.JobHostUnits},
  2640  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  2641  		startAliveMachine{"1", ""},
  2642  		setMachineStatus{"1", status.Started, ""},
  2643  		addCharm{"mysql"},
  2644  		addApplication{name: "mysql", charm: "mysql"},
  2645  		setApplicationExposed{"mysql", true},
  2646  		addAliveUnit{"mysql", "1"},
  2647  		setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  2648  		addCharmWithRevision{addCharm{"mysql"}, "cs", 2},
  2649  		setApplicationCharm{"mysql", "cs:quantal/mysql-2"},
  2650  		addCharmPlaceholder{"mysql", 23},
  2651  
  2652  		expect{
  2653  			what: "applications and units with correct charm status",
  2654  			output: M{
  2655  				"model": model,
  2656  				"machines": M{
  2657  					"0": machine0,
  2658  					"1": machine1,
  2659  				},
  2660  				"applications": M{
  2661  					"mysql": mysqlCharm(M{
  2662  						"charm":          "cs:quantal/mysql-2",
  2663  						"charm-rev":      2,
  2664  						"can-upgrade-to": "cs:quantal/mysql-23",
  2665  						"exposed":        true,
  2666  						"application-status": M{
  2667  							"current": "active",
  2668  							"since":   "01 Apr 15 01:23+10:00",
  2669  						},
  2670  						"units": M{
  2671  							"mysql/0": M{
  2672  								"machine": "1",
  2673  								"workload-status": M{
  2674  									"current": "active",
  2675  									"since":   "01 Apr 15 01:23+10:00",
  2676  								},
  2677  								"juju-status": M{
  2678  									"current": "idle",
  2679  									"since":   "01 Apr 15 01:23+10:00",
  2680  								},
  2681  								"upgrading-from": "cs:quantal/mysql-1",
  2682  								"public-address": "10.0.1.1",
  2683  							},
  2684  						},
  2685  						"endpoint-bindings": M{
  2686  							"server":         "",
  2687  							"server-admin":   "",
  2688  							"metrics-client": "",
  2689  						},
  2690  					}),
  2691  				},
  2692  				"storage": M{},
  2693  				"controller": M{
  2694  					"timestamp": "15:04:05+07:00",
  2695  				},
  2696  			},
  2697  		},
  2698  	),
  2699  	test( // 16
  2700  		"application with local charm not shown as out of date",
  2701  		addMachine{machineId: "0", job: state.JobManageModel},
  2702  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  2703  		startAliveMachine{"0", ""},
  2704  		setMachineStatus{"0", status.Started, ""},
  2705  		addMachine{machineId: "1", job: state.JobHostUnits},
  2706  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  2707  		startAliveMachine{"1", ""},
  2708  		setMachineStatus{"1", status.Started, ""},
  2709  		addCharm{"mysql"},
  2710  		addApplication{name: "mysql", charm: "mysql"},
  2711  		setApplicationExposed{"mysql", true},
  2712  		addAliveUnit{"mysql", "1"},
  2713  		setUnitCharmURL{"mysql/0", "cs:quantal/mysql-1"},
  2714  		addCharmWithRevision{addCharm{"mysql"}, "local", 1},
  2715  		setApplicationCharm{"mysql", "local:quantal/mysql-1"},
  2716  		addCharmPlaceholder{"mysql", 23},
  2717  
  2718  		expect{
  2719  			what: "applications and units with correct charm status",
  2720  			output: M{
  2721  				"model": model,
  2722  				"machines": M{
  2723  					"0": machine0,
  2724  					"1": machine1,
  2725  				},
  2726  				"applications": M{
  2727  					"mysql": mysqlCharm(M{
  2728  						"charm":        "local:quantal/mysql-1",
  2729  						"charm-origin": "local",
  2730  						"exposed":      true,
  2731  						"application-status": M{
  2732  							"current": "active",
  2733  							"since":   "01 Apr 15 01:23+10:00",
  2734  						},
  2735  						"units": M{
  2736  							"mysql/0": M{
  2737  								"machine": "1",
  2738  								"workload-status": M{
  2739  									"current": "active",
  2740  									"since":   "01 Apr 15 01:23+10:00",
  2741  								},
  2742  								"juju-status": M{
  2743  									"current": "idle",
  2744  									"since":   "01 Apr 15 01:23+10:00",
  2745  								},
  2746  								"upgrading-from": "cs:quantal/mysql-1",
  2747  								"public-address": "10.0.1.1",
  2748  							},
  2749  						},
  2750  						"endpoint-bindings": M{
  2751  							"server":         "",
  2752  							"server-admin":   "",
  2753  							"metrics-client": "",
  2754  						},
  2755  					}),
  2756  				},
  2757  				"storage": M{},
  2758  				"controller": M{
  2759  					"timestamp": "15:04:05+07:00",
  2760  				},
  2761  			},
  2762  		},
  2763  	),
  2764  	test( // 17
  2765  		"deploy two applications; set meter statuses on one",
  2766  		addMachine{machineId: "0", job: state.JobManageModel},
  2767  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  2768  		startAliveMachine{"0", ""},
  2769  		setMachineStatus{"0", status.Started, ""},
  2770  
  2771  		addMachine{machineId: "1", job: state.JobHostUnits},
  2772  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  2773  		startAliveMachine{"1", ""},
  2774  		setMachineStatus{"1", status.Started, ""},
  2775  
  2776  		addMachine{machineId: "2", job: state.JobHostUnits},
  2777  		setAddresses{"2", network.NewAddresses("10.0.2.1")},
  2778  		startAliveMachine{"2", ""},
  2779  		setMachineStatus{"2", status.Started, ""},
  2780  
  2781  		addMachine{machineId: "3", job: state.JobHostUnits},
  2782  		setAddresses{"3", network.NewAddresses("10.0.3.1")},
  2783  		startAliveMachine{"3", ""},
  2784  		setMachineStatus{"3", status.Started, ""},
  2785  		setMachineInstanceStatus{"3", status.Started, "I am number three"},
  2786  
  2787  		addMachine{machineId: "4", job: state.JobHostUnits},
  2788  		setAddresses{"4", network.NewAddresses("10.0.4.1")},
  2789  		startAliveMachine{"4", ""},
  2790  		setMachineStatus{"4", status.Started, ""},
  2791  
  2792  		addCharm{"mysql"},
  2793  		addApplication{name: "mysql", charm: "mysql"},
  2794  		setApplicationExposed{"mysql", true},
  2795  
  2796  		addCharm{"metered"},
  2797  		addApplication{name: "applicationwithmeterstatus", charm: "metered"},
  2798  
  2799  		addAliveUnit{"mysql", "1"},
  2800  		addAliveUnit{"applicationwithmeterstatus", "2"},
  2801  		addAliveUnit{"applicationwithmeterstatus", "3"},
  2802  		addAliveUnit{"applicationwithmeterstatus", "4"},
  2803  
  2804  		setApplicationExposed{"mysql", true},
  2805  
  2806  		setAgentStatus{"mysql/0", status.Idle, "", nil},
  2807  		setUnitStatus{"mysql/0", status.Active, "", nil},
  2808  		setAgentStatus{"applicationwithmeterstatus/0", status.Idle, "", nil},
  2809  		setUnitStatus{"applicationwithmeterstatus/0", status.Active, "", nil},
  2810  		setAgentStatus{"applicationwithmeterstatus/1", status.Idle, "", nil},
  2811  		setUnitStatus{"applicationwithmeterstatus/1", status.Active, "", nil},
  2812  		setAgentStatus{"applicationwithmeterstatus/2", status.Idle, "", nil},
  2813  		setUnitStatus{"applicationwithmeterstatus/2", status.Active, "", nil},
  2814  
  2815  		setUnitMeterStatus{"applicationwithmeterstatus/1", "GREEN", "test green status"},
  2816  		setUnitMeterStatus{"applicationwithmeterstatus/2", "RED", "test red status"},
  2817  
  2818  		expect{
  2819  			what: "simulate just the two applications and a bootstrap node",
  2820  			output: M{
  2821  				"model": model,
  2822  				"machines": M{
  2823  					"0": machine0,
  2824  					"1": machine1,
  2825  					"2": machine2,
  2826  					"3": machine3,
  2827  					"4": machine4,
  2828  				},
  2829  				"applications": M{
  2830  					"mysql": mysqlCharm(M{
  2831  						"exposed": true,
  2832  						"application-status": M{
  2833  							"current": "active",
  2834  							"since":   "01 Apr 15 01:23+10:00",
  2835  						},
  2836  						"units": M{
  2837  							"mysql/0": M{
  2838  								"machine": "1",
  2839  								"workload-status": M{
  2840  									"current": "active",
  2841  									"since":   "01 Apr 15 01:23+10:00",
  2842  								},
  2843  								"juju-status": M{
  2844  									"current": "idle",
  2845  									"since":   "01 Apr 15 01:23+10:00",
  2846  								},
  2847  								"public-address": "10.0.1.1",
  2848  							},
  2849  						},
  2850  						"endpoint-bindings": M{
  2851  							"server":         "",
  2852  							"server-admin":   "",
  2853  							"metrics-client": "",
  2854  						},
  2855  					}),
  2856  
  2857  					"applicationwithmeterstatus": meteredCharm(M{
  2858  						"application-status": M{
  2859  							"current": "active",
  2860  							"since":   "01 Apr 15 01:23+10:00",
  2861  						},
  2862  						"units": M{
  2863  							"applicationwithmeterstatus/0": M{
  2864  								"machine": "2",
  2865  								"workload-status": M{
  2866  									"current": "active",
  2867  									"since":   "01 Apr 15 01:23+10:00",
  2868  								},
  2869  								"juju-status": M{
  2870  									"current": "idle",
  2871  									"since":   "01 Apr 15 01:23+10:00",
  2872  								},
  2873  								"public-address": "10.0.2.1",
  2874  							},
  2875  							"applicationwithmeterstatus/1": M{
  2876  								"machine": "3",
  2877  								"workload-status": M{
  2878  									"current": "active",
  2879  									"since":   "01 Apr 15 01:23+10:00",
  2880  								},
  2881  								"juju-status": M{
  2882  									"current": "idle",
  2883  									"since":   "01 Apr 15 01:23+10:00",
  2884  								},
  2885  								"meter-status": M{
  2886  									"color":   "green",
  2887  									"message": "test green status",
  2888  								},
  2889  								"public-address": "10.0.3.1",
  2890  							},
  2891  							"applicationwithmeterstatus/2": M{
  2892  								"machine": "4",
  2893  								"workload-status": M{
  2894  									"current": "active",
  2895  									"since":   "01 Apr 15 01:23+10:00",
  2896  								},
  2897  								"juju-status": M{
  2898  									"current": "idle",
  2899  									"since":   "01 Apr 15 01:23+10:00",
  2900  								},
  2901  								"meter-status": M{
  2902  									"color":   "red",
  2903  									"message": "test red status",
  2904  								},
  2905  								"public-address": "10.0.4.1",
  2906  							},
  2907  						},
  2908  					}),
  2909  				},
  2910  				"storage": M{},
  2911  				"controller": M{
  2912  					"timestamp": "15:04:05+07:00",
  2913  				},
  2914  			},
  2915  		},
  2916  	),
  2917  	test( // 18
  2918  		"upgrade available",
  2919  		setToolsUpgradeAvailable{},
  2920  		expect{
  2921  			what: "upgrade availability should be shown in model-status",
  2922  			output: M{
  2923  				"model": M{
  2924  					"name":              "controller",
  2925  					"type":              "iaas",
  2926  					"controller":        "kontroll",
  2927  					"cloud":             "dummy",
  2928  					"region":            "dummy-region",
  2929  					"version":           "1.2.3",
  2930  					"upgrade-available": "1.2.4",
  2931  					"model-status": M{
  2932  						"current": "available",
  2933  						"since":   "01 Apr 15 01:23+10:00",
  2934  					},
  2935  					"sla": "unsupported",
  2936  				},
  2937  				"machines":     M{},
  2938  				"applications": M{},
  2939  				"storage":      M{},
  2940  				"controller": M{
  2941  					"timestamp": "15:04:05+07:00",
  2942  				},
  2943  			},
  2944  			stderr: "Model \"controller\" is empty.\n",
  2945  		},
  2946  	),
  2947  	test( // 19
  2948  		"consistent workload version",
  2949  		addMachine{machineId: "0", job: state.JobManageModel},
  2950  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  2951  		startAliveMachine{"0", ""},
  2952  		setMachineStatus{"0", status.Started, ""},
  2953  
  2954  		addCharm{"mysql"},
  2955  		addApplication{name: "mysql", charm: "mysql"},
  2956  
  2957  		addMachine{machineId: "1", job: state.JobHostUnits},
  2958  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  2959  		startAliveMachine{"1", ""},
  2960  		setMachineStatus{"1", status.Started, ""},
  2961  		addAliveUnit{"mysql", "1"},
  2962  		setUnitWorkloadVersion{"mysql/0", "the best!"},
  2963  
  2964  		expect{
  2965  			what: "application and unit with correct workload version",
  2966  			output: M{
  2967  				"model": model,
  2968  				"machines": M{
  2969  					"0": machine0,
  2970  					"1": machine1,
  2971  				},
  2972  				"applications": M{
  2973  					"mysql": mysqlCharm(M{
  2974  						"version": "the best!",
  2975  						"application-status": M{
  2976  							"current": "waiting",
  2977  							"message": "waiting for machine",
  2978  							"since":   "01 Apr 15 01:23+10:00",
  2979  						},
  2980  						"units": M{
  2981  							"mysql/0": M{
  2982  								"machine": "1",
  2983  								"workload-status": M{
  2984  									"current": "waiting",
  2985  									"message": "waiting for machine",
  2986  									"since":   "01 Apr 15 01:23+10:00",
  2987  								},
  2988  								"juju-status": M{
  2989  									"current": "allocating",
  2990  									"since":   "01 Apr 15 01:23+10:00",
  2991  								},
  2992  								"public-address": "10.0.1.1",
  2993  							},
  2994  						},
  2995  						"endpoint-bindings": M{
  2996  							"server":         "",
  2997  							"server-admin":   "",
  2998  							"metrics-client": "",
  2999  						},
  3000  					}),
  3001  				},
  3002  				"storage": M{},
  3003  				"controller": M{
  3004  					"timestamp": "15:04:05+07:00",
  3005  				},
  3006  			},
  3007  		},
  3008  	),
  3009  	test( // 20
  3010  		"mixed workload version",
  3011  		addMachine{machineId: "0", job: state.JobManageModel},
  3012  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  3013  		startAliveMachine{"0", ""},
  3014  		setMachineStatus{"0", status.Started, ""},
  3015  
  3016  		addCharm{"mysql"},
  3017  		addApplication{name: "mysql", charm: "mysql"},
  3018  
  3019  		addMachine{machineId: "1", job: state.JobHostUnits},
  3020  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  3021  		startAliveMachine{"1", ""},
  3022  		setMachineStatus{"1", status.Started, ""},
  3023  		addAliveUnit{"mysql", "1"},
  3024  		setUnitWorkloadVersion{"mysql/0", "the best!"},
  3025  
  3026  		addMachine{machineId: "2", job: state.JobHostUnits},
  3027  		setAddresses{"2", network.NewAddresses("10.0.2.1")},
  3028  		startAliveMachine{"2", ""},
  3029  		setMachineStatus{"2", status.Started, ""},
  3030  		addAliveUnit{"mysql", "2"},
  3031  		setUnitWorkloadVersion{"mysql/1", "not as good"},
  3032  
  3033  		expect{
  3034  			what: "application and unit with correct workload version",
  3035  			output: M{
  3036  				"model": model,
  3037  				"machines": M{
  3038  					"0": machine0,
  3039  					"1": machine1,
  3040  					"2": machine2,
  3041  				},
  3042  				"applications": M{
  3043  					"mysql": mysqlCharm(M{
  3044  						"version": "not as good",
  3045  						"application-status": M{
  3046  							"current": "waiting",
  3047  							"message": "waiting for machine",
  3048  							"since":   "01 Apr 15 01:23+10:00",
  3049  						},
  3050  						"units": M{
  3051  							"mysql/0": M{
  3052  								"machine": "1",
  3053  								"workload-status": M{
  3054  									"current": "waiting",
  3055  									"message": "waiting for machine",
  3056  									"since":   "01 Apr 15 01:23+10:00",
  3057  								},
  3058  								"juju-status": M{
  3059  									"current": "allocating",
  3060  									"since":   "01 Apr 15 01:23+10:00",
  3061  								},
  3062  								"public-address": "10.0.1.1",
  3063  							},
  3064  							"mysql/1": M{
  3065  								"machine": "2",
  3066  								"workload-status": M{
  3067  									"current": "waiting",
  3068  									"message": "waiting for machine",
  3069  									"since":   "01 Apr 15 01:23+10:00",
  3070  								},
  3071  								"juju-status": M{
  3072  									"current": "allocating",
  3073  									"since":   "01 Apr 15 01:23+10:00",
  3074  								},
  3075  								"public-address": "10.0.2.1",
  3076  							},
  3077  						},
  3078  						"endpoint-bindings": M{
  3079  							"server":         "",
  3080  							"server-admin":   "",
  3081  							"metrics-client": "",
  3082  						},
  3083  					}),
  3084  				},
  3085  				"storage": M{},
  3086  				"controller": M{
  3087  					"timestamp": "15:04:05+07:00",
  3088  				},
  3089  			},
  3090  		},
  3091  	),
  3092  	test( // 21
  3093  		"instance with localhost addresses",
  3094  		addMachine{machineId: "0", job: state.JobManageModel},
  3095  		setAddresses{"0", []network.Address{
  3096  			network.NewScopedAddress("10.0.0.1", network.ScopeCloudLocal),
  3097  			network.NewScopedAddress("127.0.0.1", network.ScopeMachineLocal),
  3098  			// TODO(macgreagoir) setAddresses step method needs to
  3099  			// set netmask correctly before we can test IPv6
  3100  			// loopback.
  3101  			// network.NewScopedAddress("::1", network.ScopeMachineLocal),
  3102  		}},
  3103  		startAliveMachine{"0", ""},
  3104  		setMachineStatus{"0", status.Started, ""},
  3105  		expect{
  3106  			what: "machine 0 has localhost addresses that should not display",
  3107  			output: M{
  3108  				"model": model,
  3109  				"machines": M{
  3110  					"0": machine0,
  3111  				},
  3112  				"applications": M{},
  3113  				"storage":      M{},
  3114  				"controller": M{
  3115  					"timestamp": "15:04:05+07:00",
  3116  				},
  3117  			},
  3118  		},
  3119  	),
  3120  	test( // 22
  3121  		"instance with IPv6 addresses",
  3122  		addMachine{machineId: "0", cons: machineCons, job: state.JobManageModel},
  3123  		setAddresses{"0", []network.Address{
  3124  			network.NewScopedAddress("2001:db8::1", network.ScopeCloudLocal),
  3125  			// TODO(macgreagoir) setAddresses step method needs to
  3126  			// set netmask correctly before we can test IPv6
  3127  			// loopback.
  3128  			// network.NewScopedAddress("::1", network.ScopeMachineLocal),
  3129  		}},
  3130  		startAliveMachine{"0", ""},
  3131  		setMachineStatus{"0", status.Started, ""},
  3132  		expect{
  3133  			what: "machine 0 has an IPv6 address",
  3134  			output: M{
  3135  				"model": model,
  3136  				"machines": M{
  3137  					"0": M{
  3138  						"juju-status": M{
  3139  							"current": "started",
  3140  							"since":   "01 Apr 15 01:23+10:00",
  3141  						},
  3142  						"dns-name":     "2001:db8::1",
  3143  						"ip-addresses": []string{"2001:db8::1"},
  3144  						"instance-id":  "controller-0",
  3145  						"machine-status": M{
  3146  							"current": "pending",
  3147  							"since":   "01 Apr 15 01:23+10:00",
  3148  						},
  3149  						"series": "quantal",
  3150  						"network-interfaces": M{
  3151  							"eth0": M{
  3152  								"ip-addresses": []string{"2001:db8::1"},
  3153  								"mac-address":  "aa:bb:cc:dd:ee:ff",
  3154  								"is-up":        true,
  3155  							},
  3156  						},
  3157  						"constraints":              "cores=2 mem=8192M root-disk=8192M",
  3158  						"hardware":                 "arch=amd64 cores=2 mem=8192M root-disk=8192M",
  3159  						"controller-member-status": "adding-vote",
  3160  					},
  3161  				},
  3162  				"applications": M{},
  3163  				"storage":      M{},
  3164  				"controller": M{
  3165  					"timestamp": "15:04:05+07:00",
  3166  				},
  3167  			},
  3168  		},
  3169  	),
  3170  	test( // 23
  3171  		"a remote application",
  3172  		addMachine{machineId: "0", job: state.JobManageModel},
  3173  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  3174  		startAliveMachine{"0", ""},
  3175  		setMachineStatus{"0", status.Started, ""},
  3176  		addMachine{machineId: "1", job: state.JobHostUnits},
  3177  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  3178  		startAliveMachine{"1", ""},
  3179  		setMachineStatus{"1", status.Started, ""},
  3180  
  3181  		addCharm{"wordpress"},
  3182  		addApplication{name: "wordpress", charm: "wordpress"},
  3183  		addAliveUnit{"wordpress", "1"},
  3184  
  3185  		addCharm{"mysql"},
  3186  		addRemoteApplication{name: "hosted-mysql", url: "me/model.mysql", charm: "mysql", endpoints: []string{"server"}},
  3187  		relateApplications{"wordpress", "hosted-mysql", ""},
  3188  
  3189  		expect{
  3190  			what: "a remote application",
  3191  			output: M{
  3192  				"model": model,
  3193  				"machines": M{
  3194  					"0": machine0,
  3195  					"1": machine1,
  3196  				},
  3197  				"application-endpoints": M{
  3198  					"hosted-mysql": M{
  3199  						"url": "me/model.mysql",
  3200  						"endpoints": M{
  3201  							"server": M{
  3202  								"interface": "mysql",
  3203  								"role":      "provider",
  3204  							},
  3205  						},
  3206  						"application-status": M{
  3207  							"current": "unknown",
  3208  							"since":   "01 Apr 15 01:23+10:00",
  3209  						},
  3210  						"relations": M{
  3211  							"server": L{"wordpress"},
  3212  						},
  3213  					},
  3214  				},
  3215  				"applications": M{
  3216  					"wordpress": wordpressCharm(M{
  3217  						"application-status": M{
  3218  							"current": "waiting",
  3219  							"message": "waiting for machine",
  3220  							"since":   "01 Apr 15 01:23+10:00",
  3221  						},
  3222  						"relations": M{
  3223  							"db": L{"hosted-mysql"},
  3224  						},
  3225  						"units": M{
  3226  							"wordpress/0": M{
  3227  								"machine": "1",
  3228  								"workload-status": M{
  3229  									"current": "waiting",
  3230  									"message": "waiting for machine",
  3231  									"since":   "01 Apr 15 01:23+10:00",
  3232  								},
  3233  								"juju-status": M{
  3234  									"current": "allocating",
  3235  									"since":   "01 Apr 15 01:23+10:00",
  3236  								},
  3237  								"public-address": "10.0.1.1",
  3238  							},
  3239  						},
  3240  						"endpoint-bindings": M{
  3241  							"monitoring-port": "",
  3242  							"url":             "",
  3243  							"admin-api":       "",
  3244  							"cache":           "",
  3245  							"db":              "",
  3246  							"db-client":       "",
  3247  							"foo-bar":         "",
  3248  							"logging-dir":     "",
  3249  						},
  3250  					}),
  3251  				},
  3252  				"storage": M{},
  3253  				"controller": M{
  3254  					"timestamp": "15:04:05+07:00",
  3255  				},
  3256  			},
  3257  		},
  3258  	),
  3259  	test( // 24
  3260  		"set meter status on the model",
  3261  		setSLA{"advanced"},
  3262  		setModelMeterStatus{"RED", "status message"},
  3263  		expect{
  3264  			what: "simulate just the two applications and a bootstrap node",
  3265  			output: M{
  3266  				"model": M{
  3267  					"name":       "controller",
  3268  					"type":       "iaas",
  3269  					"controller": "kontroll",
  3270  					"cloud":      "dummy",
  3271  					"region":     "dummy-region",
  3272  					"version":    "1.2.3",
  3273  					"model-status": M{
  3274  						"current": "available",
  3275  						"since":   "01 Apr 15 01:23+10:00",
  3276  					},
  3277  					"meter-status": M{
  3278  						"color":   "red",
  3279  						"message": "status message",
  3280  					},
  3281  					"sla": "advanced",
  3282  				},
  3283  				"machines":     M{},
  3284  				"applications": M{},
  3285  				"storage":      M{},
  3286  				"controller": M{
  3287  					"timestamp": "15:04:05+07:00",
  3288  				},
  3289  			},
  3290  			stderr: "Model \"controller\" is empty.\n",
  3291  		},
  3292  	),
  3293  	test( // 25
  3294  		"set sla on the model",
  3295  		setSLA{"advanced"},
  3296  		expect{
  3297  			what: "set sla on the model",
  3298  			output: M{
  3299  				"model": M{
  3300  					"name":       "controller",
  3301  					"type":       "iaas",
  3302  					"controller": "kontroll",
  3303  					"cloud":      "dummy",
  3304  					"region":     "dummy-region",
  3305  					"version":    "1.2.3",
  3306  					"model-status": M{
  3307  						"current": "available",
  3308  						"since":   "01 Apr 15 01:23+10:00",
  3309  					},
  3310  					"sla": "advanced",
  3311  				},
  3312  				"machines":     M{},
  3313  				"applications": M{},
  3314  				"storage":      M{},
  3315  				"controller": M{
  3316  					"timestamp": "15:04:05+07:00",
  3317  				},
  3318  			},
  3319  			stderr: "Model \"controller\" is empty.\n",
  3320  		},
  3321  	),
  3322  	test( //26
  3323  		"deploy application with endpoint bound to space",
  3324  		addMachine{machineId: "0", job: state.JobManageModel},
  3325  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  3326  		startAliveMachine{"0", ""},
  3327  		setMachineStatus{"0", status.Started, ""},
  3328  		addMachine{machineId: "1", job: state.JobHostUnits},
  3329  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  3330  		startAliveMachine{"1", ""},
  3331  		setMachineStatus{"1", status.Started, ""},
  3332  
  3333  		addSpace{"myspace1"},
  3334  
  3335  		addCharm{"wordpress"},
  3336  		addApplication{name: "wordpress", charm: "wordpress", binding: map[string]string{"db-client": "", "logging-dir": "", "cache": "", "db": "myspace1", "monitoring-port": "", "url": "", "admin-api": "", "foo-bar": ""}},
  3337  		addAliveUnit{"wordpress", "1"},
  3338  
  3339  		scopedExpect{
  3340  			output: M{
  3341  				"model": M{
  3342  					"region":  "dummy-region",
  3343  					"version": "1.2.3",
  3344  					"model-status": M{
  3345  						"current": "available",
  3346  						"since":   "01 Apr 15 01:23+10:00",
  3347  					},
  3348  					"type":       "iaas",
  3349  					"sla":        "unsupported",
  3350  					"name":       "controller",
  3351  					"controller": "kontroll",
  3352  					"cloud":      "dummy",
  3353  				},
  3354  				"machines": M{
  3355  					"1": M{
  3356  						"juju-status": M{
  3357  							"current": "started",
  3358  							"since":   "01 Apr 15 01:23+10:00",
  3359  						},
  3360  						"dns-name":     "10.0.1.1",
  3361  						"ip-addresses": []string{"10.0.1.1"},
  3362  						"instance-id":  "controller-1",
  3363  						"machine-status": M{
  3364  							"current": "pending",
  3365  							"since":   "01 Apr 15 01:23+10:00",
  3366  						},
  3367  						"series": "quantal",
  3368  						"network-interfaces": M{
  3369  							"eth0": M{
  3370  								"ip-addresses": []string{"10.0.1.1"},
  3371  								"mac-address":  "aa:bb:cc:dd:ee:ff",
  3372  								"is-up":        bool(true),
  3373  							},
  3374  						},
  3375  						"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
  3376  					},
  3377  					"0": M{
  3378  						"series": "quantal",
  3379  						"network-interfaces": M{
  3380  							"eth0": M{
  3381  								"ip-addresses": []string{"10.0.0.1"},
  3382  								"mac-address":  "aa:bb:cc:dd:ee:ff",
  3383  								"is-up":        bool(true),
  3384  							},
  3385  						},
  3386  						"controller-member-status": "adding-vote",
  3387  						"dns-name":                 "10.0.0.1",
  3388  						"ip-addresses":             []string{"10.0.0.1"},
  3389  						"instance-id":              "controller-0",
  3390  						"machine-status": M{
  3391  							"current": "pending",
  3392  							"since":   "01 Apr 15 01:23+10:00",
  3393  						},
  3394  						"juju-status": M{
  3395  							"current": "started",
  3396  							"since":   "01 Apr 15 01:23+10:00",
  3397  						},
  3398  						"hardware": "arch=amd64 cores=1 mem=1024M root-disk=8192M",
  3399  					},
  3400  				},
  3401  				"applications": M{
  3402  					"wordpress": M{
  3403  						"series":     "quantal",
  3404  						"os":         "ubuntu",
  3405  						"charm-name": "wordpress",
  3406  						"exposed":    bool(false),
  3407  						"units": M{
  3408  							"wordpress/0": M{
  3409  								"public-address": "10.0.1.1",
  3410  								"workload-status": M{
  3411  									"current": "waiting",
  3412  									"message": "waiting for machine",
  3413  									"since":   "01 Apr 15 01:23+10:00",
  3414  								},
  3415  								"juju-status": M{
  3416  									"current": "allocating",
  3417  									"since":   "01 Apr 15 01:23+10:00",
  3418  								},
  3419  								"machine": "1",
  3420  							},
  3421  						},
  3422  						"charm":        "cs:quantal/wordpress-3",
  3423  						"charm-origin": "jujucharms",
  3424  						"charm-rev":    int(3),
  3425  						"application-status": M{
  3426  							"current": "waiting",
  3427  							"message": "waiting for machine",
  3428  							"since":   "01 Apr 15 01:23+10:00",
  3429  						},
  3430  						"endpoint-bindings": M{
  3431  							"cache":           "",
  3432  							"db":              "myspace1",
  3433  							"db-client":       "",
  3434  							"foo-bar":         "",
  3435  							"logging-dir":     "",
  3436  							"monitoring-port": "",
  3437  							"url":             "",
  3438  							"admin-api":       "",
  3439  						},
  3440  					},
  3441  				},
  3442  				"storage": M{},
  3443  				"controller": M{
  3444  					"timestamp": "15:04:05+07:00",
  3445  				},
  3446  			},
  3447  		},
  3448  	),
  3449  	test( // 27
  3450  		"application with lxd profiles",
  3451  		addMachine{machineId: "0", job: state.JobManageModel},
  3452  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  3453  		startAliveMachine{"0", ""},
  3454  		setMachineStatus{"0", status.Started, ""},
  3455  		addMachine{machineId: "1", job: state.JobHostUnits},
  3456  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  3457  		startAliveMachine{"1", ""},
  3458  		setMachineStatus{"1", status.Started, ""},
  3459  		setCharmProfiles{"1", []string{"juju-controller-lxd-profile-1"}},
  3460  		addCharm{"lxd-profile"},
  3461  		addApplication{name: "lxd-profile", charm: "lxd-profile"},
  3462  		setApplicationExposed{"lxd-profile", true},
  3463  		addAliveUnit{"lxd-profile", "1"},
  3464  		setUnitCharmURL{"lxd-profile/0", "cs:quantal/lxd-profile-0"},
  3465  		addCharmWithRevision{addCharm{"lxd-profile"}, "local", 1},
  3466  		setApplicationCharm{"lxd-profile", "local:quantal/lxd-profile-1"},
  3467  		addCharmPlaceholder{"lxd-profile", 23},
  3468  		expect{
  3469  			what: "applications and units with correct lxd profile charm status",
  3470  			output: M{
  3471  				"model": model,
  3472  				"machines": M{
  3473  					"0": machine0,
  3474  					"1": machine1WithLXDProfile,
  3475  				},
  3476  				"applications": M{
  3477  					"lxd-profile": lxdProfileCharm(M{
  3478  						"charm":        "local:quantal/lxd-profile-1",
  3479  						"charm-origin": "local",
  3480  						"exposed":      true,
  3481  						"application-status": M{
  3482  							"current": "active",
  3483  							"since":   "01 Apr 15 01:23+10:00",
  3484  						},
  3485  						"units": M{
  3486  							"lxd-profile/0": M{
  3487  								"machine": "1",
  3488  								"workload-status": M{
  3489  									"current": "active",
  3490  									"since":   "01 Apr 15 01:23+10:00",
  3491  								},
  3492  								"juju-status": M{
  3493  									"current": "idle",
  3494  									"since":   "01 Apr 15 01:23+10:00",
  3495  								},
  3496  								"upgrading-from": "cs:quantal/lxd-profile-0",
  3497  								"public-address": "10.0.1.1",
  3498  							},
  3499  						},
  3500  						"endpoint-bindings": M{
  3501  							"another": "",
  3502  							"ubuntu":  "",
  3503  						},
  3504  					}),
  3505  				},
  3506  				"storage": M{},
  3507  				"controller": M{
  3508  					"timestamp": "15:04:05+07:00",
  3509  				},
  3510  			},
  3511  		},
  3512  	),
  3513  }
  3514  
  3515  func mysqlCharm(extras M) M {
  3516  	charm := M{
  3517  		"charm":        "cs:quantal/mysql-1",
  3518  		"charm-origin": "jujucharms",
  3519  		"charm-name":   "mysql",
  3520  		"charm-rev":    1,
  3521  		"series":       "quantal",
  3522  		"os":           "ubuntu",
  3523  		"exposed":      false,
  3524  	}
  3525  	for key, value := range extras {
  3526  		charm[key] = value
  3527  	}
  3528  	return charm
  3529  }
  3530  
  3531  func lxdProfileCharm(extras M) M {
  3532  	charm := M{
  3533  		"charm":        "cs:quantal/lxd-profile-0",
  3534  		"charm-origin": "jujucharms",
  3535  		"charm-name":   "lxd-profile",
  3536  		"charm-rev":    1,
  3537  		"series":       "quantal",
  3538  		"os":           "ubuntu",
  3539  		"exposed":      false,
  3540  	}
  3541  	for key, value := range extras {
  3542  		charm[key] = value
  3543  	}
  3544  	return charm
  3545  }
  3546  
  3547  func meteredCharm(extras M) M {
  3548  	charm := M{
  3549  		"charm":        "cs:quantal/metered-1",
  3550  		"charm-origin": "jujucharms",
  3551  		"charm-name":   "metered",
  3552  		"charm-rev":    1,
  3553  		"series":       "quantal",
  3554  		"os":           "ubuntu",
  3555  		"exposed":      false,
  3556  	}
  3557  	for key, value := range extras {
  3558  		charm[key] = value
  3559  	}
  3560  	return charm
  3561  }
  3562  
  3563  func dummyCharm(extras M) M {
  3564  	charm := M{
  3565  		"charm":        "cs:quantal/dummy-1",
  3566  		"charm-origin": "jujucharms",
  3567  		"charm-name":   "dummy",
  3568  		"charm-rev":    1,
  3569  		"series":       "quantal",
  3570  		"os":           "ubuntu",
  3571  		"exposed":      false,
  3572  	}
  3573  	for key, value := range extras {
  3574  		charm[key] = value
  3575  	}
  3576  	return charm
  3577  }
  3578  
  3579  func wordpressCharm(extras M) M {
  3580  	charm := M{
  3581  		"charm":        "cs:quantal/wordpress-3",
  3582  		"charm-origin": "jujucharms",
  3583  		"charm-name":   "wordpress",
  3584  		"charm-rev":    3,
  3585  		"series":       "quantal",
  3586  		"os":           "ubuntu",
  3587  		"exposed":      false,
  3588  	}
  3589  	for key, value := range extras {
  3590  		charm[key] = value
  3591  	}
  3592  	return charm
  3593  }
  3594  
  3595  // TODO(dfc) test failing components by destructively mutating the state under the hood
  3596  
  3597  // sometimes you just need to skip the tests for windows (environment variables etc)
  3598  type skipTestOnWindows struct{}
  3599  
  3600  func (skipTestOnWindows) step(c *gc.C, ctx *context) {
  3601  	if runtime.GOOS == "windows" {
  3602  		ctx.skipTest = true
  3603  	}
  3604  }
  3605  
  3606  type setSLA struct {
  3607  	level string
  3608  }
  3609  
  3610  func (s setSLA) step(c *gc.C, ctx *context) {
  3611  	err := ctx.st.SetSLA(s.level, "test-user", []byte(""))
  3612  	c.Assert(err, jc.ErrorIsNil)
  3613  }
  3614  
  3615  type addMachine struct {
  3616  	machineId string
  3617  	cons      constraints.Value
  3618  	job       state.MachineJob
  3619  }
  3620  
  3621  func (am addMachine) step(c *gc.C, ctx *context) {
  3622  	m, err := ctx.st.AddOneMachine(state.MachineTemplate{
  3623  		Series:      "quantal",
  3624  		Constraints: am.cons,
  3625  		Jobs:        []state.MachineJob{am.job},
  3626  	})
  3627  	c.Assert(err, jc.ErrorIsNil)
  3628  	c.Assert(m.Id(), gc.Equals, am.machineId)
  3629  }
  3630  
  3631  type addContainer struct {
  3632  	parentId  string
  3633  	machineId string
  3634  	job       state.MachineJob
  3635  }
  3636  
  3637  func (ac addContainer) step(c *gc.C, ctx *context) {
  3638  	template := state.MachineTemplate{
  3639  		Series: "quantal",
  3640  		Jobs:   []state.MachineJob{ac.job},
  3641  	}
  3642  	m, err := ctx.st.AddMachineInsideMachine(template, ac.parentId, instance.LXD)
  3643  	c.Assert(err, jc.ErrorIsNil)
  3644  	c.Assert(m.Id(), gc.Equals, ac.machineId)
  3645  }
  3646  
  3647  type startMachine struct {
  3648  	machineId string
  3649  }
  3650  
  3651  func (sm startMachine) step(c *gc.C, ctx *context) {
  3652  	m, err := ctx.st.Machine(sm.machineId)
  3653  	c.Assert(err, jc.ErrorIsNil)
  3654  	cons, err := m.Constraints()
  3655  	c.Assert(err, jc.ErrorIsNil)
  3656  	cfg, err := ctx.st.ControllerConfig()
  3657  	c.Assert(err, jc.ErrorIsNil)
  3658  	inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, environscontext.NewCloudCallContext(), cfg.ControllerUUID(), m.Id(), cons)
  3659  	err = m.SetProvisioned(inst.Id(), "", "fake_nonce", hc)
  3660  	c.Assert(err, jc.ErrorIsNil)
  3661  }
  3662  
  3663  type startMissingMachine struct {
  3664  	machineId string
  3665  }
  3666  
  3667  func (sm startMissingMachine) step(c *gc.C, ctx *context) {
  3668  	m, err := ctx.st.Machine(sm.machineId)
  3669  	c.Assert(err, jc.ErrorIsNil)
  3670  	cons, err := m.Constraints()
  3671  	c.Assert(err, jc.ErrorIsNil)
  3672  	cfg, err := ctx.st.ControllerConfig()
  3673  	c.Assert(err, jc.ErrorIsNil)
  3674  	_, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, environscontext.NewCloudCallContext(), cfg.ControllerUUID(), m.Id(), cons)
  3675  	err = m.SetProvisioned("i-missing", "", "fake_nonce", hc)
  3676  	c.Assert(err, jc.ErrorIsNil)
  3677  	// lp:1558657
  3678  	now := time.Now()
  3679  	s := status.StatusInfo{
  3680  		Status:  status.Unknown,
  3681  		Message: "missing",
  3682  		Since:   &now,
  3683  	}
  3684  	err = m.SetInstanceStatus(s)
  3685  	c.Assert(err, jc.ErrorIsNil)
  3686  }
  3687  
  3688  type startAliveMachine struct {
  3689  	machineId   string
  3690  	displayName string
  3691  }
  3692  
  3693  func (sam startAliveMachine) step(c *gc.C, ctx *context) {
  3694  	m, err := ctx.st.Machine(sam.machineId)
  3695  	c.Assert(err, jc.ErrorIsNil)
  3696  	pinger := ctx.setAgentPresence(c, m)
  3697  	cons, err := m.Constraints()
  3698  	c.Assert(err, jc.ErrorIsNil)
  3699  	cfg, err := ctx.st.ControllerConfig()
  3700  	c.Assert(err, jc.ErrorIsNil)
  3701  	inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, environscontext.NewCloudCallContext(), cfg.ControllerUUID(), m.Id(), cons)
  3702  	err = m.SetProvisioned(inst.Id(), sam.displayName, "fake_nonce", hc)
  3703  	c.Assert(err, jc.ErrorIsNil)
  3704  	ctx.pingers[m.Id()] = pinger
  3705  }
  3706  
  3707  type startMachineWithHardware struct {
  3708  	machineId string
  3709  	hc        instance.HardwareCharacteristics
  3710  }
  3711  
  3712  func (sm startMachineWithHardware) step(c *gc.C, ctx *context) {
  3713  	m, err := ctx.st.Machine(sm.machineId)
  3714  	c.Assert(err, jc.ErrorIsNil)
  3715  	pinger := ctx.setAgentPresence(c, m)
  3716  	cons, err := m.Constraints()
  3717  	c.Assert(err, jc.ErrorIsNil)
  3718  	cfg, err := ctx.st.ControllerConfig()
  3719  	c.Assert(err, jc.ErrorIsNil)
  3720  	inst, _ := testing.AssertStartInstanceWithConstraints(c, ctx.env, environscontext.NewCloudCallContext(), cfg.ControllerUUID(), m.Id(), cons)
  3721  	err = m.SetProvisioned(inst.Id(), "", "fake_nonce", &sm.hc)
  3722  	c.Assert(err, jc.ErrorIsNil)
  3723  	ctx.pingers[m.Id()] = pinger
  3724  }
  3725  
  3726  type startAliveMachineWithDisplayName struct {
  3727  	machineId   string
  3728  	displayName string
  3729  }
  3730  
  3731  func (sm startAliveMachineWithDisplayName) step(c *gc.C, ctx *context) {
  3732  	m, err := ctx.st.Machine(sm.machineId)
  3733  	c.Assert(err, jc.ErrorIsNil)
  3734  	pinger := ctx.setAgentPresence(c, m)
  3735  	cons, err := m.Constraints()
  3736  	c.Assert(err, jc.ErrorIsNil)
  3737  	cfg, err := ctx.st.ControllerConfig()
  3738  	c.Assert(err, jc.ErrorIsNil)
  3739  	inst, hc := testing.AssertStartInstanceWithConstraints(c, ctx.env, environscontext.NewCloudCallContext(), cfg.ControllerUUID(), m.Id(), cons)
  3740  	err = m.SetProvisioned(inst.Id(), sm.displayName, "fake_nonce", hc)
  3741  	c.Assert(err, jc.ErrorIsNil)
  3742  	ctx.pingers[m.Id()] = pinger
  3743  	_, displayName, err := m.InstanceNames()
  3744  	c.Assert(err, jc.ErrorIsNil)
  3745  	c.Assert(displayName, gc.Equals, sm.displayName)
  3746  }
  3747  
  3748  type setMachineInstanceStatus struct {
  3749  	machineId string
  3750  	Status    status.Status
  3751  	Message   string
  3752  }
  3753  
  3754  func (sm setMachineInstanceStatus) step(c *gc.C, ctx *context) {
  3755  	m, err := ctx.st.Machine(sm.machineId)
  3756  	c.Assert(err, jc.ErrorIsNil)
  3757  	now := time.Now()
  3758  	s := status.StatusInfo{
  3759  		Status:  sm.Status,
  3760  		Message: sm.Message,
  3761  		Since:   &now,
  3762  	}
  3763  	err = m.SetInstanceStatus(s)
  3764  	c.Assert(err, jc.ErrorIsNil)
  3765  }
  3766  
  3767  type addSpace struct {
  3768  	spaceName string
  3769  }
  3770  
  3771  func (sp addSpace) step(c *gc.C, ctx *context) {
  3772  	f := factory.NewFactory(ctx.st, ctx.pool)
  3773  	f.MakeSpace(c, &factory.SpaceParams{
  3774  		Name: sp.spaceName, ProviderID: network.Id("provider"), IsPublic: true})
  3775  }
  3776  
  3777  type setAddresses struct {
  3778  	machineId string
  3779  	addresses []network.Address
  3780  }
  3781  
  3782  func (sa setAddresses) step(c *gc.C, ctx *context) {
  3783  	m, err := ctx.st.Machine(sa.machineId)
  3784  	c.Assert(err, jc.ErrorIsNil)
  3785  	err = m.SetProviderAddresses(sa.addresses...)
  3786  	c.Assert(err, jc.ErrorIsNil)
  3787  	addrs := make([]state.LinkLayerDeviceAddress, len(sa.addresses))
  3788  	lldevs := make([]state.LinkLayerDeviceArgs, len(sa.addresses))
  3789  	for i, address := range sa.addresses {
  3790  		devName := fmt.Sprintf("eth%d", i)
  3791  		macAddr := "aa:bb:cc:dd:ee:ff"
  3792  		configMethod := state.StaticAddress
  3793  		devType := state.EthernetDevice
  3794  		if address.Scope == network.ScopeMachineLocal ||
  3795  			address.Value == "localhost" {
  3796  			devName = "lo"
  3797  			macAddr = "00:00:00:00:00:00"
  3798  			configMethod = state.LoopbackAddress
  3799  			devType = state.LoopbackDevice
  3800  		}
  3801  		lldevs[i] = state.LinkLayerDeviceArgs{
  3802  			Name:       devName,
  3803  			MACAddress: macAddr, // TODO(macgreagoir) Enough for first pass
  3804  			IsUp:       true,
  3805  			Type:       devType,
  3806  		}
  3807  		addrs[i] = state.LinkLayerDeviceAddress{
  3808  			DeviceName:   devName,
  3809  			ConfigMethod: configMethod,
  3810  			// TODO(macgreagoir) Enough for first pass, but
  3811  			// incorrect for IPv4 loopback, and breaks IPv6
  3812  			// loopback.
  3813  			CIDRAddress: fmt.Sprintf("%s/24", address.Value)}
  3814  	}
  3815  	// TODO(macgreagoir) Let these go for now, before this turns into a test for setting lldevs and addrs.
  3816  	// err = m.SetLinkLayerDevices(lldevs...)
  3817  	// c.Assert(err, jc.ErrorIsNil)
  3818  	_ = m.SetLinkLayerDevices(lldevs...)
  3819  	// err = m.SetDevicesAddresses(addrs...)
  3820  	// c.Assert(err, jc.ErrorIsNil)
  3821  	_ = m.SetDevicesAddresses(addrs...)
  3822  }
  3823  
  3824  type setTools struct {
  3825  	machineId string
  3826  	version   version.Binary
  3827  }
  3828  
  3829  func (st setTools) step(c *gc.C, ctx *context) {
  3830  	m, err := ctx.st.Machine(st.machineId)
  3831  	c.Assert(err, jc.ErrorIsNil)
  3832  	err = m.SetAgentVersion(st.version)
  3833  	c.Assert(err, jc.ErrorIsNil)
  3834  }
  3835  
  3836  type setUnitTools struct {
  3837  	unitName string
  3838  	version  version.Binary
  3839  }
  3840  
  3841  func (st setUnitTools) step(c *gc.C, ctx *context) {
  3842  	m, err := ctx.st.Unit(st.unitName)
  3843  	c.Assert(err, jc.ErrorIsNil)
  3844  	err = m.SetAgentVersion(st.version)
  3845  	c.Assert(err, jc.ErrorIsNil)
  3846  }
  3847  
  3848  type addCharm struct {
  3849  	name string
  3850  }
  3851  
  3852  func (ac addCharm) addCharmStep(c *gc.C, ctx *context, scheme string, rev int) {
  3853  	ch := testcharms.Repo.CharmDir(ac.name)
  3854  	name := ch.Meta().Name
  3855  	curl := charm.MustParseURL(fmt.Sprintf("%s:quantal/%s-%d", scheme, name, rev))
  3856  	info := state.CharmInfo{
  3857  		Charm:       ch,
  3858  		ID:          curl,
  3859  		StoragePath: "dummy-path",
  3860  		SHA256:      fmt.Sprintf("%s-%d-sha256", name, rev),
  3861  	}
  3862  	dummy, err := ctx.st.AddCharm(info)
  3863  	c.Assert(err, jc.ErrorIsNil)
  3864  	ctx.charms[ac.name] = dummy
  3865  }
  3866  
  3867  func (ac addCharm) step(c *gc.C, ctx *context) {
  3868  	ch := testcharms.Repo.CharmDir(ac.name)
  3869  	ac.addCharmStep(c, ctx, "cs", ch.Revision())
  3870  }
  3871  
  3872  type addCharmWithRevision struct {
  3873  	addCharm
  3874  	scheme string
  3875  	rev    int
  3876  }
  3877  
  3878  func (ac addCharmWithRevision) step(c *gc.C, ctx *context) {
  3879  	ac.addCharmStep(c, ctx, ac.scheme, ac.rev)
  3880  }
  3881  
  3882  type addApplication struct {
  3883  	name    string
  3884  	charm   string
  3885  	binding map[string]string
  3886  	cons    constraints.Value
  3887  }
  3888  
  3889  func (as addApplication) step(c *gc.C, ctx *context) {
  3890  	ch, ok := ctx.charms[as.charm]
  3891  	c.Assert(ok, jc.IsTrue)
  3892  	app, err := ctx.st.AddApplication(state.AddApplicationArgs{Name: as.name, Charm: ch, EndpointBindings: as.binding})
  3893  	c.Assert(err, jc.ErrorIsNil)
  3894  	if app.IsPrincipal() {
  3895  		err = app.SetConstraints(as.cons)
  3896  		c.Assert(err, jc.ErrorIsNil)
  3897  	}
  3898  }
  3899  
  3900  type addRemoteApplication struct {
  3901  	name            string
  3902  	url             string
  3903  	charm           string
  3904  	endpoints       []string
  3905  	isConsumerProxy bool
  3906  }
  3907  
  3908  func (as addRemoteApplication) step(c *gc.C, ctx *context) {
  3909  	ch, ok := ctx.charms[as.charm]
  3910  	c.Assert(ok, jc.IsTrue)
  3911  	var endpoints []charm.Relation
  3912  	for _, ep := range as.endpoints {
  3913  		r, ok := ch.Meta().Requires[ep]
  3914  		if !ok {
  3915  			r, ok = ch.Meta().Provides[ep]
  3916  		}
  3917  		c.Assert(ok, jc.IsTrue)
  3918  		endpoints = append(endpoints, r)
  3919  	}
  3920  	_, err := ctx.st.AddRemoteApplication(state.AddRemoteApplicationParams{
  3921  		Name:            as.name,
  3922  		URL:             as.url,
  3923  		SourceModel:     coretesting.ModelTag,
  3924  		Endpoints:       endpoints,
  3925  		IsConsumerProxy: as.isConsumerProxy,
  3926  	})
  3927  	c.Assert(err, jc.ErrorIsNil)
  3928  }
  3929  
  3930  type addApplicationOffer struct {
  3931  	name            string
  3932  	owner           string
  3933  	applicationName string
  3934  	endpoints       []string
  3935  }
  3936  
  3937  func (ao addApplicationOffer) step(c *gc.C, ctx *context) {
  3938  	endpoints := make(map[string]string)
  3939  	for _, ep := range ao.endpoints {
  3940  		endpoints[ep] = ep
  3941  	}
  3942  	offers := state.NewApplicationOffers(ctx.st)
  3943  	_, err := offers.AddOffer(crossmodel.AddApplicationOfferArgs{
  3944  		OfferName:       ao.name,
  3945  		Owner:           ao.owner,
  3946  		ApplicationName: ao.applicationName,
  3947  		Endpoints:       endpoints,
  3948  	})
  3949  	c.Assert(err, jc.ErrorIsNil)
  3950  }
  3951  
  3952  type addOfferConnection struct {
  3953  	sourceModelUUID string
  3954  	name            string
  3955  	username        string
  3956  	relationKey     string
  3957  }
  3958  
  3959  func (oc addOfferConnection) step(c *gc.C, ctx *context) {
  3960  	rel, err := ctx.st.KeyRelation(oc.relationKey)
  3961  	c.Assert(err, jc.ErrorIsNil)
  3962  	offer, err := state.NewApplicationOffers(ctx.st).ApplicationOffer(oc.name)
  3963  	c.Assert(err, jc.ErrorIsNil)
  3964  	_, err = ctx.st.AddOfferConnection(state.AddOfferConnectionParams{
  3965  		SourceModelUUID: oc.sourceModelUUID,
  3966  		OfferUUID:       offer.OfferUUID,
  3967  		Username:        oc.username,
  3968  		RelationId:      rel.Id(),
  3969  		RelationKey:     rel.Tag().Id(),
  3970  	})
  3971  	c.Assert(err, jc.ErrorIsNil)
  3972  }
  3973  
  3974  type setApplicationExposed struct {
  3975  	name    string
  3976  	exposed bool
  3977  }
  3978  
  3979  func (sse setApplicationExposed) step(c *gc.C, ctx *context) {
  3980  	s, err := ctx.st.Application(sse.name)
  3981  	c.Assert(err, jc.ErrorIsNil)
  3982  	err = s.ClearExposed()
  3983  	c.Assert(err, jc.ErrorIsNil)
  3984  	if sse.exposed {
  3985  		err = s.SetExposed()
  3986  		c.Assert(err, jc.ErrorIsNil)
  3987  	}
  3988  }
  3989  
  3990  type setApplicationCharm struct {
  3991  	name  string
  3992  	charm string
  3993  }
  3994  
  3995  func (ssc setApplicationCharm) step(c *gc.C, ctx *context) {
  3996  	fmt.Println("HERE")
  3997  	ch, err := ctx.st.Charm(charm.MustParseURL(ssc.charm))
  3998  	c.Assert(err, jc.ErrorIsNil)
  3999  	s, err := ctx.st.Application(ssc.name)
  4000  	c.Assert(err, jc.ErrorIsNil)
  4001  	cfg := state.SetCharmConfig{Charm: ch}
  4002  	err = s.SetCharm(cfg)
  4003  	c.Assert(err, jc.ErrorIsNil)
  4004  }
  4005  
  4006  type addCharmPlaceholder struct {
  4007  	name string
  4008  	rev  int
  4009  }
  4010  
  4011  func (ac addCharmPlaceholder) step(c *gc.C, ctx *context) {
  4012  	ch := testcharms.Repo.CharmDir(ac.name)
  4013  	name := ch.Meta().Name
  4014  	curl := charm.MustParseURL(fmt.Sprintf("cs:quantal/%s-%d", name, ac.rev))
  4015  	err := ctx.st.AddStoreCharmPlaceholder(curl)
  4016  	c.Assert(err, jc.ErrorIsNil)
  4017  }
  4018  
  4019  type addUnit struct {
  4020  	applicationName string
  4021  	machineId       string
  4022  }
  4023  
  4024  func (au addUnit) step(c *gc.C, ctx *context) {
  4025  	s, err := ctx.st.Application(au.applicationName)
  4026  	c.Assert(err, jc.ErrorIsNil)
  4027  	u, err := s.AddUnit(state.AddUnitParams{})
  4028  	c.Assert(err, jc.ErrorIsNil)
  4029  	m, err := ctx.st.Machine(au.machineId)
  4030  	c.Assert(err, jc.ErrorIsNil)
  4031  	err = u.AssignToMachine(m)
  4032  	c.Assert(err, jc.ErrorIsNil)
  4033  	ctx.statusSetter.SetAgentStatus(u.Tag().String(), corepresence.Missing)
  4034  }
  4035  
  4036  type addAliveUnit struct {
  4037  	applicationName string
  4038  	machineId       string
  4039  }
  4040  
  4041  func (aau addAliveUnit) step(c *gc.C, ctx *context) {
  4042  	s, err := ctx.st.Application(aau.applicationName)
  4043  	c.Assert(err, jc.ErrorIsNil)
  4044  	u, err := s.AddUnit(state.AddUnitParams{})
  4045  	c.Assert(err, jc.ErrorIsNil)
  4046  	pinger := ctx.setAgentPresence(c, u)
  4047  	m, err := ctx.st.Machine(aau.machineId)
  4048  	c.Assert(err, jc.ErrorIsNil)
  4049  	err = u.AssignToMachine(m)
  4050  	c.Assert(err, jc.ErrorIsNil)
  4051  	ctx.pingers[u.Name()] = pinger
  4052  }
  4053  
  4054  type setUnitsAlive struct {
  4055  	applicationName string
  4056  }
  4057  
  4058  func (sua setUnitsAlive) step(c *gc.C, ctx *context) {
  4059  	s, err := ctx.st.Application(sua.applicationName)
  4060  	c.Assert(err, jc.ErrorIsNil)
  4061  	us, err := s.AllUnits()
  4062  	c.Assert(err, jc.ErrorIsNil)
  4063  	for _, u := range us {
  4064  		ctx.pingers[u.Name()] = ctx.setAgentPresence(c, u)
  4065  	}
  4066  }
  4067  
  4068  type setUnitMeterStatus struct {
  4069  	unitName string
  4070  	color    string
  4071  	message  string
  4072  }
  4073  
  4074  func (s setUnitMeterStatus) step(c *gc.C, ctx *context) {
  4075  	u, err := ctx.st.Unit(s.unitName)
  4076  	c.Assert(err, jc.ErrorIsNil)
  4077  	err = u.SetMeterStatus(s.color, s.message)
  4078  	c.Assert(err, jc.ErrorIsNil)
  4079  }
  4080  
  4081  type setModelMeterStatus struct {
  4082  	color   string
  4083  	message string
  4084  }
  4085  
  4086  func (s setModelMeterStatus) step(c *gc.C, ctx *context) {
  4087  	m, err := ctx.st.Model()
  4088  	c.Assert(err, jc.ErrorIsNil)
  4089  	err = m.SetMeterStatus(s.color, s.message)
  4090  	c.Assert(err, jc.ErrorIsNil)
  4091  }
  4092  
  4093  type setUnitAsLeader struct {
  4094  	unitName string
  4095  }
  4096  
  4097  func (s setUnitAsLeader) step(c *gc.C, ctx *context) {
  4098  	u, err := ctx.st.Unit(s.unitName)
  4099  	c.Assert(err, jc.ErrorIsNil)
  4100  	target := ctx.st.LeaseNotifyTarget(
  4101  		ioutil.Discard,
  4102  		loggo.GetLogger("status_internal_test"),
  4103  	)
  4104  	target.Claimed(
  4105  		lease.Key{"application-leadership", ctx.st.ModelUUID(), u.ApplicationName()},
  4106  		u.Name(),
  4107  	)
  4108  }
  4109  
  4110  type setUnitStatus struct {
  4111  	unitName   string
  4112  	status     status.Status
  4113  	statusInfo string
  4114  	statusData map[string]interface{}
  4115  }
  4116  
  4117  func (sus setUnitStatus) step(c *gc.C, ctx *context) {
  4118  	u, err := ctx.st.Unit(sus.unitName)
  4119  	c.Assert(err, jc.ErrorIsNil)
  4120  	// lp:1558657
  4121  	now := time.Now()
  4122  	s := status.StatusInfo{
  4123  		Status:  sus.status,
  4124  		Message: sus.statusInfo,
  4125  		Data:    sus.statusData,
  4126  		Since:   &now,
  4127  	}
  4128  	err = u.SetStatus(s)
  4129  	c.Assert(err, jc.ErrorIsNil)
  4130  }
  4131  
  4132  type setAgentStatus struct {
  4133  	unitName   string
  4134  	status     status.Status
  4135  	statusInfo string
  4136  	statusData map[string]interface{}
  4137  }
  4138  
  4139  func (sus setAgentStatus) step(c *gc.C, ctx *context) {
  4140  	u, err := ctx.st.Unit(sus.unitName)
  4141  	c.Assert(err, jc.ErrorIsNil)
  4142  	// lp:1558657
  4143  	now := time.Now()
  4144  	sInfo := status.StatusInfo{
  4145  		Status:  sus.status,
  4146  		Message: sus.statusInfo,
  4147  		Data:    sus.statusData,
  4148  		Since:   &now,
  4149  	}
  4150  	err = u.SetAgentStatus(sInfo)
  4151  	c.Assert(err, jc.ErrorIsNil)
  4152  }
  4153  
  4154  type setUnitCharmURL struct {
  4155  	unitName string
  4156  	charm    string
  4157  }
  4158  
  4159  func (uc setUnitCharmURL) step(c *gc.C, ctx *context) {
  4160  	u, err := ctx.st.Unit(uc.unitName)
  4161  	c.Assert(err, jc.ErrorIsNil)
  4162  	curl := charm.MustParseURL(uc.charm)
  4163  	err = u.SetCharmURL(curl)
  4164  	c.Assert(err, jc.ErrorIsNil)
  4165  	// lp:1558657
  4166  	now := time.Now()
  4167  	s := status.StatusInfo{
  4168  		Status:  status.Active,
  4169  		Message: "",
  4170  		Since:   &now,
  4171  	}
  4172  	err = u.SetStatus(s)
  4173  	c.Assert(err, jc.ErrorIsNil)
  4174  	sInfo := status.StatusInfo{
  4175  		Status:  status.Idle,
  4176  		Message: "",
  4177  		Since:   &now,
  4178  	}
  4179  	err = u.SetAgentStatus(sInfo)
  4180  	c.Assert(err, jc.ErrorIsNil)
  4181  
  4182  }
  4183  
  4184  type setUnitWorkloadVersion struct {
  4185  	unitName string
  4186  	version  string
  4187  }
  4188  
  4189  func (wv setUnitWorkloadVersion) step(c *gc.C, ctx *context) {
  4190  	u, err := ctx.st.Unit(wv.unitName)
  4191  	c.Assert(err, jc.ErrorIsNil)
  4192  	err = u.SetWorkloadVersion(wv.version)
  4193  	c.Assert(err, jc.ErrorIsNil)
  4194  }
  4195  
  4196  type openUnitPort struct {
  4197  	unitName string
  4198  	protocol string
  4199  	number   int
  4200  }
  4201  
  4202  func (oup openUnitPort) step(c *gc.C, ctx *context) {
  4203  	u, err := ctx.st.Unit(oup.unitName)
  4204  	c.Assert(err, jc.ErrorIsNil)
  4205  	err = u.OpenPort(oup.protocol, oup.number)
  4206  	c.Assert(err, jc.ErrorIsNil)
  4207  }
  4208  
  4209  type ensureDyingUnit struct {
  4210  	unitName string
  4211  }
  4212  
  4213  func (e ensureDyingUnit) step(c *gc.C, ctx *context) {
  4214  	u, err := ctx.st.Unit(e.unitName)
  4215  	c.Assert(err, jc.ErrorIsNil)
  4216  	err = u.Destroy()
  4217  	c.Assert(err, jc.ErrorIsNil)
  4218  	c.Assert(u.Life(), gc.Equals, state.Dying)
  4219  }
  4220  
  4221  type ensureDyingApplication struct {
  4222  	applicationName string
  4223  }
  4224  
  4225  func (e ensureDyingApplication) step(c *gc.C, ctx *context) {
  4226  	svc, err := ctx.st.Application(e.applicationName)
  4227  	c.Assert(err, jc.ErrorIsNil)
  4228  	err = svc.Destroy()
  4229  	c.Assert(err, jc.ErrorIsNil)
  4230  	err = svc.Refresh()
  4231  	c.Assert(err, jc.ErrorIsNil)
  4232  	c.Assert(svc.Life(), gc.Equals, state.Dying)
  4233  }
  4234  
  4235  type ensureDeadMachine struct {
  4236  	machineId string
  4237  }
  4238  
  4239  func (e ensureDeadMachine) step(c *gc.C, ctx *context) {
  4240  	m, err := ctx.st.Machine(e.machineId)
  4241  	c.Assert(err, jc.ErrorIsNil)
  4242  	err = m.EnsureDead()
  4243  	c.Assert(err, jc.ErrorIsNil)
  4244  	c.Assert(m.Life(), gc.Equals, state.Dead)
  4245  }
  4246  
  4247  type setMachineStatus struct {
  4248  	machineId  string
  4249  	status     status.Status
  4250  	statusInfo string
  4251  }
  4252  
  4253  func (sms setMachineStatus) step(c *gc.C, ctx *context) {
  4254  	// lp:1558657
  4255  	now := time.Now()
  4256  	m, err := ctx.st.Machine(sms.machineId)
  4257  	c.Assert(err, jc.ErrorIsNil)
  4258  	sInfo := status.StatusInfo{
  4259  		Status:  sms.status,
  4260  		Message: sms.statusInfo,
  4261  		Since:   &now,
  4262  	}
  4263  	err = m.SetStatus(sInfo)
  4264  	c.Assert(err, jc.ErrorIsNil)
  4265  }
  4266  
  4267  type relateApplications struct {
  4268  	ep1, ep2 string
  4269  	status   string
  4270  }
  4271  
  4272  func (rs relateApplications) step(c *gc.C, ctx *context) {
  4273  	eps, err := ctx.st.InferEndpoints(rs.ep1, rs.ep2)
  4274  	c.Assert(err, jc.ErrorIsNil)
  4275  	rel, err := ctx.st.AddRelation(eps...)
  4276  	c.Assert(err, jc.ErrorIsNil)
  4277  	s := rs.status
  4278  	if s == "" {
  4279  		s = "joined"
  4280  	}
  4281  	err = rel.SetStatus(status.StatusInfo{Status: status.Status(s)})
  4282  	c.Assert(err, jc.ErrorIsNil)
  4283  }
  4284  
  4285  type addSubordinate struct {
  4286  	prinUnit       string
  4287  	subApplication string
  4288  }
  4289  
  4290  func (as addSubordinate) step(c *gc.C, ctx *context) {
  4291  	u, err := ctx.st.Unit(as.prinUnit)
  4292  	c.Assert(err, jc.ErrorIsNil)
  4293  	eps, err := ctx.st.InferEndpoints(u.ApplicationName(), as.subApplication)
  4294  	c.Assert(err, jc.ErrorIsNil)
  4295  	rel, err := ctx.st.EndpointsRelation(eps...)
  4296  	c.Assert(err, jc.ErrorIsNil)
  4297  	ru, err := rel.Unit(u)
  4298  	c.Assert(err, jc.ErrorIsNil)
  4299  	err = ru.EnterScope(nil)
  4300  	c.Assert(err, jc.ErrorIsNil)
  4301  }
  4302  
  4303  type setCharmProfiles struct {
  4304  	machineId string
  4305  	profiles  []string
  4306  }
  4307  
  4308  func (s setCharmProfiles) step(c *gc.C, ctx *context) {
  4309  	m, err := ctx.st.Machine(s.machineId)
  4310  	c.Assert(err, jc.ErrorIsNil)
  4311  	err = m.SetCharmProfiles(s.profiles)
  4312  	c.Assert(err, jc.ErrorIsNil)
  4313  }
  4314  
  4315  type scopedExpect struct {
  4316  	what   string
  4317  	scope  []string
  4318  	output M
  4319  	stderr string
  4320  }
  4321  
  4322  type expect struct {
  4323  	what   string
  4324  	output M
  4325  	stderr string
  4326  }
  4327  
  4328  // substituteFakeTime replaces all key values
  4329  // in actual status output with a known fake value.
  4330  func substituteFakeTime(c *gc.C, key string, in []byte, expectIsoTime bool) []byte {
  4331  	// This regexp will work for yaml and json.
  4332  	exp := regexp.MustCompile(`(?P<key>"?` + key + `"?:\ ?)(?P<quote>"?)(?P<timestamp>[^("|\n)]*)*"?`)
  4333  	// Before the substitution is done, check that the timestamp produced
  4334  	// by status is in the correct format.
  4335  	if matches := exp.FindStringSubmatch(string(in)); matches != nil {
  4336  		for i, name := range exp.SubexpNames() {
  4337  			if name != "timestamp" {
  4338  				continue
  4339  			}
  4340  			timeFormat := "02 Jan 2006 15:04:05Z07:00"
  4341  			if expectIsoTime {
  4342  				timeFormat = "2006-01-02 15:04:05Z"
  4343  			}
  4344  			_, err := time.Parse(timeFormat, matches[i])
  4345  			c.Assert(err, jc.ErrorIsNil)
  4346  		}
  4347  	}
  4348  
  4349  	out := exp.ReplaceAllString(string(in), `$key$quote<timestamp>$quote`)
  4350  	// Substitute a made up time used in our expected output.
  4351  	out = strings.Replace(out, "<timestamp>", "01 Apr 15 01:23+10:00", -1)
  4352  	return []byte(out)
  4353  }
  4354  
  4355  // substituteFakeTimestamp replaces all key values for a given timestamp
  4356  // in actual status output with a known fake value.
  4357  func substituteFakeTimestamp(c *gc.C, in []byte, expectIsoTime bool) []byte {
  4358  	timeFormat := "15:04:05Z07:00"
  4359  	output := strings.Replace(timeFormat, "Z", "+", -1)
  4360  	if expectIsoTime {
  4361  		timeFormat = "15:04:05Z"
  4362  		output = "15:04:05"
  4363  	}
  4364  	// This regexp will work for any input type
  4365  	exp := regexp.MustCompile(`(?P<timestamp>[0-9]{2}:[0-9]{2}:[0-9]{2}((Z|\+|\-)([0-9]{2}:[0-9]{2})?)?)`)
  4366  	if matches := exp.FindStringSubmatch(string(in)); matches != nil {
  4367  		for i, name := range exp.SubexpNames() {
  4368  			if name != "timestamp" {
  4369  				continue
  4370  			}
  4371  			value := matches[i]
  4372  			if num := len(value); num == 8 {
  4373  				value = fmt.Sprintf("%sZ", value)
  4374  			} else if num == 14 && (expectIsoTime || value[8] == 'Z') {
  4375  				value = fmt.Sprintf("%sZ", value[:8])
  4376  			}
  4377  			_, err := time.Parse(timeFormat, value)
  4378  			c.Assert(err, jc.ErrorIsNil)
  4379  		}
  4380  	}
  4381  
  4382  	out := exp.ReplaceAllString(string(in), `<timestamp>`)
  4383  	// Substitute a made up time used in our expected output.
  4384  	out = strings.Replace(out, "<timestamp>", output, -1)
  4385  	return []byte(out)
  4386  }
  4387  
  4388  // substituteSpacingBetweenTimestampAndNotes forces the spacing between the
  4389  // headers Timestamp and Notes to be consistent regardless of the time. This
  4390  // happens because we're dealing with the result of the strings of stdout and
  4391  // not with any useable AST
  4392  func substituteSpacingBetweenTimestampAndNotes(c *gc.C, in []byte) []byte {
  4393  	exp := regexp.MustCompile(`Timestamp(?P<spacing>\s+)Notes`)
  4394  	result := exp.ReplaceAllString(string(in), fmt.Sprintf("Timestamp%sNotes", strings.Repeat(" ", 7)))
  4395  	return []byte(result)
  4396  }
  4397  
  4398  func (e scopedExpect) step(c *gc.C, ctx *context) {
  4399  	c.Logf("\nexpect: %s %s\n", e.what, strings.Join(e.scope, " "))
  4400  
  4401  	// Now execute the command for each format.
  4402  	for _, format := range statusFormats {
  4403  		c.Logf("format %q", format.name)
  4404  		// Run command with the required format.
  4405  		args := []string{"--format", format.name}
  4406  		if ctx.expectIsoTime {
  4407  			args = append(args, "--utc")
  4408  		}
  4409  		args = append(args, e.scope...)
  4410  		c.Logf("running status %s", strings.Join(args, " "))
  4411  		code, stdout, stderr := runStatus(c, args...)
  4412  		c.Assert(code, gc.Equals, 0)
  4413  		c.Assert(string(stderr), gc.Equals, e.stderr)
  4414  
  4415  		// Prepare the output in the same format.
  4416  		buf, err := format.marshal(e.output)
  4417  		c.Assert(err, jc.ErrorIsNil)
  4418  
  4419  		// we have to force the timestamp into the correct format as the model
  4420  		// is in string.
  4421  		buf = substituteFakeTimestamp(c, buf, ctx.expectIsoTime)
  4422  
  4423  		expected := make(M)
  4424  		err = format.unmarshal(buf, &expected)
  4425  		c.Assert(err, jc.ErrorIsNil)
  4426  
  4427  		// Check the output is as expected.
  4428  		actual := make(M)
  4429  		out := substituteFakeTime(c, "since", stdout, ctx.expectIsoTime)
  4430  		out = substituteFakeTimestamp(c, out, ctx.expectIsoTime)
  4431  		err = format.unmarshal(out, &actual)
  4432  		c.Assert(err, jc.ErrorIsNil)
  4433  		pretty.Ldiff(c, actual, expected)
  4434  		c.Assert(actual, jc.DeepEquals, expected)
  4435  	}
  4436  }
  4437  
  4438  func (e expect) step(c *gc.C, ctx *context) {
  4439  	scopedExpect{e.what, nil, e.output, e.stderr}.step(c, ctx)
  4440  }
  4441  
  4442  type setToolsUpgradeAvailable struct{}
  4443  
  4444  func (ua setToolsUpgradeAvailable) step(c *gc.C, ctx *context) {
  4445  	model, err := ctx.st.Model()
  4446  	c.Assert(err, jc.ErrorIsNil)
  4447  	err = model.UpdateLatestToolsVersion(nextVersion)
  4448  	c.Assert(err, jc.ErrorIsNil)
  4449  }
  4450  
  4451  func (s *StatusSuite) TestStatusAllFormats(c *gc.C) {
  4452  	for i, t := range statusTests {
  4453  		c.Logf("test %d: %s", i, t.summary)
  4454  		func(t testCase) {
  4455  			// Prepare context and run all steps to setup.
  4456  			ctx := s.newContext(c)
  4457  			defer s.resetContext(c, ctx)
  4458  			ctx.run(c, t.steps)
  4459  		}(t)
  4460  	}
  4461  }
  4462  
  4463  func (s *StatusSuite) TestMigrationInProgress(c *gc.C) {
  4464  	// This test isn't part of statusTests because migrations can't be
  4465  	// run on controller models.
  4466  	st := s.setupMigrationTest(c)
  4467  	defer st.Close()
  4468  
  4469  	expected := M{
  4470  		"model": M{
  4471  			"name":       "hosted",
  4472  			"type":       "iaas",
  4473  			"controller": "kontroll",
  4474  			"cloud":      "dummy",
  4475  			"region":     "dummy-region",
  4476  			"version":    "1.2.3",
  4477  			"model-status": M{
  4478  				"current": "busy",
  4479  				"since":   "01 Apr 15 01:23+10:00",
  4480  				"message": "migrating: foo bar",
  4481  			},
  4482  			"sla": "unsupported",
  4483  		},
  4484  		"machines":     M{},
  4485  		"applications": M{},
  4486  		"storage":      M{},
  4487  		"controller": M{
  4488  			"timestamp": "15:04:05+07:00",
  4489  		},
  4490  	}
  4491  
  4492  	for _, format := range statusFormats {
  4493  		code, stdout, stderr := runStatus(c, "-m", "hosted", "--format", format.name)
  4494  		c.Check(code, gc.Equals, 0)
  4495  		c.Assert(string(stderr), gc.Equals, "Model \"hosted\" is empty.\n")
  4496  
  4497  		stdout = substituteFakeTime(c, "since", stdout, false)
  4498  		stdout = substituteFakeTimestamp(c, stdout, false)
  4499  
  4500  		// Roundtrip expected through format so that types will match.
  4501  		buf, err := format.marshal(expected)
  4502  		c.Assert(err, jc.ErrorIsNil)
  4503  		var expectedForFormat M
  4504  		err = format.unmarshal(buf, &expectedForFormat)
  4505  		c.Assert(err, jc.ErrorIsNil)
  4506  
  4507  		var actual M
  4508  		c.Assert(format.unmarshal(stdout, &actual), jc.ErrorIsNil)
  4509  		c.Check(actual, jc.DeepEquals, expectedForFormat)
  4510  	}
  4511  }
  4512  
  4513  func (s *StatusSuite) TestMigrationInProgressTabular(c *gc.C) {
  4514  	expected := `
  4515  Model   Controller  Cloud/Region        Version  SLA          Timestamp       Notes
  4516  hosted  kontroll    dummy/dummy-region  1.2.3    unsupported  15:04:05+07:00  migrating: foo bar
  4517  
  4518  `[1:]
  4519  
  4520  	st := s.setupMigrationTest(c)
  4521  	defer st.Close()
  4522  	code, stdout, stderr := runStatus(c, "-m", "hosted", "--format", "tabular")
  4523  	c.Assert(code, gc.Equals, 0)
  4524  	c.Assert(string(stderr), gc.Equals, "Model \"hosted\" is empty.\n")
  4525  
  4526  	output := substituteFakeTimestamp(c, stdout, false)
  4527  	output = substituteSpacingBetweenTimestampAndNotes(c, output)
  4528  	c.Assert(string(output), gc.Equals, expected)
  4529  }
  4530  
  4531  func (s *StatusSuite) TestMigrationInProgressAndUpgradeAvailable(c *gc.C) {
  4532  	expected := `
  4533  Model   Controller  Cloud/Region        Version  SLA          Timestamp       Notes
  4534  hosted  kontroll    dummy/dummy-region  1.2.3    unsupported  15:04:05+07:00  migrating: foo bar
  4535  
  4536  `[1:]
  4537  
  4538  	st := s.setupMigrationTest(c)
  4539  	defer st.Close()
  4540  
  4541  	model, err := st.Model()
  4542  	c.Assert(err, jc.ErrorIsNil)
  4543  	err = model.UpdateLatestToolsVersion(nextVersion)
  4544  	c.Assert(err, jc.ErrorIsNil)
  4545  
  4546  	code, stdout, stderr := runStatus(c, "-m", "hosted", "--format", "tabular")
  4547  	c.Assert(code, gc.Equals, 0)
  4548  	c.Assert(string(stderr), gc.Equals, "Model \"hosted\" is empty.\n")
  4549  
  4550  	output := substituteFakeTimestamp(c, stdout, false)
  4551  	output = substituteSpacingBetweenTimestampAndNotes(c, output)
  4552  	c.Assert(string(output), gc.Equals, expected)
  4553  }
  4554  
  4555  func (s *StatusSuite) setupMigrationTest(c *gc.C) *state.State {
  4556  	const hostedModelName = "hosted"
  4557  	const statusText = "foo bar"
  4558  
  4559  	f := factory.NewFactory(s.BackingState, s.StatePool)
  4560  	hostedSt := f.MakeModel(c, &factory.ModelParams{
  4561  		Name: hostedModelName,
  4562  	})
  4563  
  4564  	mig, err := hostedSt.CreateMigration(state.MigrationSpec{
  4565  		InitiatedBy: names.NewUserTag("admin"),
  4566  		TargetInfo: migration.TargetInfo{
  4567  			ControllerTag: names.NewControllerTag(utils.MustNewUUID().String()),
  4568  			Addrs:         []string{"1.2.3.4:5555", "4.3.2.1:6666"},
  4569  			CACert:        "cert",
  4570  			AuthTag:       names.NewUserTag("user"),
  4571  			Password:      "password",
  4572  		},
  4573  	})
  4574  	c.Assert(err, jc.ErrorIsNil)
  4575  	err = mig.SetStatusMessage(statusText)
  4576  	c.Assert(err, jc.ErrorIsNil)
  4577  
  4578  	return hostedSt
  4579  }
  4580  
  4581  type fakeAPIClient struct {
  4582  	statusReturn *params.FullStatus
  4583  	patternsUsed []string
  4584  	closeCalled  bool
  4585  }
  4586  
  4587  func (a *fakeAPIClient) Status(patterns []string) (*params.FullStatus, error) {
  4588  	a.patternsUsed = patterns
  4589  	return a.statusReturn, nil
  4590  }
  4591  
  4592  func (a *fakeAPIClient) Close() error {
  4593  	a.closeCalled = true
  4594  	return nil
  4595  }
  4596  
  4597  func (s *StatusSuite) TestStatusWithFormatSummary(c *gc.C) {
  4598  	ctx := s.newContext(c)
  4599  	defer s.resetContext(c, ctx)
  4600  	steps := []stepper{
  4601  		addMachine{machineId: "0", job: state.JobManageModel},
  4602  		setAddresses{"0", network.NewAddresses("localhost")},
  4603  		startAliveMachine{"0", "snowflake"},
  4604  		setMachineStatus{"0", status.Started, ""},
  4605  		addCharm{"wordpress"},
  4606  		addCharm{"mysql"},
  4607  		addCharm{"logging"},
  4608  		addCharm{"riak"},
  4609  		addRemoteApplication{name: "hosted-riak", url: "me/model.riak", charm: "riak", endpoints: []string{"endpoint"}},
  4610  		addApplication{name: "wordpress", charm: "wordpress"},
  4611  		setApplicationExposed{"wordpress", true},
  4612  		addMachine{machineId: "1", job: state.JobHostUnits},
  4613  		setAddresses{"1", network.NewAddresses("localhost")},
  4614  		startAliveMachine{"1", ""},
  4615  		setMachineStatus{"1", status.Started, ""},
  4616  		addAliveUnit{"wordpress", "1"},
  4617  		setAgentStatus{"wordpress/0", status.Idle, "", nil},
  4618  		setUnitStatus{"wordpress/0", status.Active, "", nil},
  4619  		addApplication{name: "mysql", charm: "mysql"},
  4620  		setApplicationExposed{"mysql", true},
  4621  		addMachine{machineId: "2", job: state.JobHostUnits},
  4622  		setAddresses{"2", network.NewAddresses("10.0.2.1")},
  4623  		startAliveMachine{"2", ""},
  4624  		setMachineStatus{"2", status.Started, ""},
  4625  		addAliveUnit{"mysql", "2"},
  4626  		setAgentStatus{"mysql/0", status.Idle, "", nil},
  4627  		setUnitStatus{"mysql/0", status.Active, "", nil},
  4628  		addApplication{name: "logging", charm: "logging"},
  4629  		setApplicationExposed{"logging", true},
  4630  		relateApplications{"wordpress", "mysql", ""},
  4631  		relateApplications{"wordpress", "logging", ""},
  4632  		relateApplications{"mysql", "logging", ""},
  4633  		addSubordinate{"wordpress/0", "logging"},
  4634  		addSubordinate{"mysql/0", "logging"},
  4635  		setUnitsAlive{"logging"},
  4636  		setAgentStatus{"logging/0", status.Idle, "", nil},
  4637  		setUnitStatus{"logging/0", status.Active, "", nil},
  4638  		setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil},
  4639  	}
  4640  	for _, s := range steps {
  4641  		s.step(c, ctx)
  4642  	}
  4643  	code, stdout, stderr := runStatus(c, "--format", "summary")
  4644  	c.Check(code, gc.Equals, 0)
  4645  	c.Check(string(stderr), gc.Equals, "")
  4646  	c.Assert(string(stdout), gc.Equals, `
  4647  Running on subnets:  127.0.0.1/8, 10.0.2.1/8  
  4648   Utilizing ports:                             
  4649        # Machines:  (3)
  4650           started:   3 
  4651                   
  4652           # Units:  (4)
  4653            active:   3 
  4654             error:   1 
  4655                   
  4656    # Applications:  (3)
  4657            logging  1/1  exposed
  4658              mysql  1/1  exposed
  4659          wordpress  1/1  exposed
  4660                   
  4661          # Remote:  (1)
  4662        hosted-riak       me/model.riak
  4663  
  4664  `[1:])
  4665  }
  4666  func (s *StatusSuite) TestStatusWithFormatOneline(c *gc.C) {
  4667  	ctx := s.newContext(c)
  4668  	defer s.resetContext(c, ctx)
  4669  	steps := []stepper{
  4670  		addMachine{machineId: "0", job: state.JobManageModel},
  4671  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  4672  		startAliveMachine{"0", "snowflake"},
  4673  		setMachineStatus{"0", status.Started, ""},
  4674  		addCharm{"wordpress"},
  4675  		addCharm{"mysql"},
  4676  		addCharm{"logging"},
  4677  
  4678  		addApplication{name: "wordpress", charm: "wordpress"},
  4679  		setApplicationExposed{"wordpress", true},
  4680  		addMachine{machineId: "1", job: state.JobHostUnits},
  4681  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  4682  		startAliveMachine{"1", ""},
  4683  		setMachineStatus{"1", status.Started, ""},
  4684  		addAliveUnit{"wordpress", "1"},
  4685  		setAgentStatus{"wordpress/0", status.Idle, "", nil},
  4686  		setUnitStatus{"wordpress/0", status.Active, "", nil},
  4687  
  4688  		addApplication{name: "mysql", charm: "mysql"},
  4689  		setApplicationExposed{"mysql", true},
  4690  		addMachine{machineId: "2", job: state.JobHostUnits},
  4691  		setAddresses{"2", network.NewAddresses("10.0.2.1")},
  4692  		startAliveMachine{"2", ""},
  4693  		setMachineStatus{"2", status.Started, ""},
  4694  		addAliveUnit{"mysql", "2"},
  4695  		setAgentStatus{"mysql/0", status.Idle, "", nil},
  4696  		setUnitStatus{"mysql/0", status.Active, "", nil},
  4697  
  4698  		addApplication{name: "logging", charm: "logging"},
  4699  		setApplicationExposed{"logging", true},
  4700  
  4701  		relateApplications{"wordpress", "mysql", ""},
  4702  		relateApplications{"wordpress", "logging", ""},
  4703  		relateApplications{"mysql", "logging", ""},
  4704  
  4705  		addSubordinate{"wordpress/0", "logging"},
  4706  		addSubordinate{"mysql/0", "logging"},
  4707  
  4708  		setUnitsAlive{"logging"},
  4709  		setAgentStatus{"logging/0", status.Idle, "", nil},
  4710  		setUnitStatus{"logging/0", status.Active, "", nil},
  4711  		setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil},
  4712  	}
  4713  
  4714  	ctx.run(c, steps)
  4715  
  4716  	const expected = `
  4717  - mysql/0: 10.0.2.1 (agent:idle, workload:active)
  4718    - logging/1: 10.0.2.1 (agent:idle, workload:error)
  4719  - wordpress/0: 10.0.1.1 (agent:idle, workload:active)
  4720    - logging/0: 10.0.1.1 (agent:idle, workload:active)
  4721  `
  4722  	assertOneLineStatus(c, expected)
  4723  }
  4724  
  4725  func assertOneLineStatus(c *gc.C, expected string) {
  4726  	code, stdout, stderr := runStatus(c, "--format", "oneline")
  4727  	c.Check(code, gc.Equals, 0)
  4728  	c.Check(string(stderr), gc.Equals, "")
  4729  	c.Assert(string(stdout), gc.Equals, expected)
  4730  
  4731  	c.Log(`Check that "short" is an alias for oneline.`)
  4732  	code, stdout, stderr = runStatus(c, "--format", "short")
  4733  	c.Check(code, gc.Equals, 0)
  4734  	c.Check(string(stderr), gc.Equals, "")
  4735  	c.Assert(string(stdout), gc.Equals, expected)
  4736  
  4737  	c.Log(`Check that "line" is an alias for oneline.`)
  4738  	code, stdout, stderr = runStatus(c, "--format", "line")
  4739  	c.Check(code, gc.Equals, 0)
  4740  	c.Check(string(stderr), gc.Equals, "")
  4741  	c.Assert(string(stdout), gc.Equals, expected)
  4742  }
  4743  
  4744  func (s *StatusSuite) prepareTabularData(c *gc.C) *context {
  4745  	ctx := s.newContext(c)
  4746  	steps := []stepper{
  4747  		setToolsUpgradeAvailable{},
  4748  		addMachine{machineId: "0", job: state.JobManageModel},
  4749  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  4750  		startMachineWithHardware{"0", instance.MustParseHardware("availability-zone=us-east-1a")},
  4751  		setMachineStatus{"0", status.Started, ""},
  4752  		addCharm{"wordpress"},
  4753  		addCharm{"mysql"},
  4754  		addCharm{"logging"},
  4755  		addCharm{"riak"},
  4756  		addRemoteApplication{name: "hosted-riak", url: "me/model.riak", charm: "riak", endpoints: []string{"endpoint"}},
  4757  		addApplication{name: "wordpress", charm: "wordpress"},
  4758  		setApplicationExposed{"wordpress", true},
  4759  		addMachine{machineId: "1", job: state.JobHostUnits},
  4760  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  4761  		startAliveMachine{"1", "snowflake"},
  4762  		setMachineStatus{"1", status.Started, ""},
  4763  		addAliveUnit{"wordpress", "1"},
  4764  		setAgentStatus{"wordpress/0", status.Idle, "", nil},
  4765  		setUnitStatus{"wordpress/0", status.Active, "", nil},
  4766  		setUnitTools{"wordpress/0", version.MustParseBinary("1.2.3-trusty-ppc")},
  4767  		addApplication{name: "mysql", charm: "mysql"},
  4768  		setApplicationExposed{"mysql", true},
  4769  		addMachine{machineId: "2", job: state.JobHostUnits},
  4770  		setAddresses{"2", network.NewAddresses("10.0.2.1")},
  4771  		startAliveMachine{"2", ""},
  4772  		setMachineStatus{"2", status.Started, ""},
  4773  		addAliveUnit{"mysql", "2"},
  4774  		setAgentStatus{"mysql/0", status.Idle, "", nil},
  4775  		setUnitStatus{
  4776  			"mysql/0",
  4777  			status.Maintenance,
  4778  			"installing all the things", nil},
  4779  		setUnitTools{"mysql/0", version.MustParseBinary("1.2.3-trusty-ppc")},
  4780  		addApplication{name: "logging", charm: "logging"},
  4781  		setApplicationExposed{"logging", true},
  4782  		relateApplications{"wordpress", "mysql", "suspended"},
  4783  		relateApplications{"wordpress", "logging", ""},
  4784  		relateApplications{"mysql", "logging", ""},
  4785  		addSubordinate{"wordpress/0", "logging"},
  4786  		addSubordinate{"mysql/0", "logging"},
  4787  		setUnitsAlive{"logging"},
  4788  		setAgentStatus{"logging/0", status.Idle, "", nil},
  4789  		setUnitStatus{"logging/0", status.Active, "", nil},
  4790  		setAgentStatus{"logging/1", status.Error, "somehow lost in all those logs", nil},
  4791  		setUnitWorkloadVersion{"logging/1", "a bit too long, really"},
  4792  		setUnitWorkloadVersion{"wordpress/0", "4.5.3"},
  4793  		setUnitWorkloadVersion{"mysql/0", "5.7.13"},
  4794  		setUnitAsLeader{"mysql/0"},
  4795  		setUnitAsLeader{"logging/1"},
  4796  		setUnitAsLeader{"wordpress/0"},
  4797  		addMachine{machineId: "3", job: state.JobHostUnits},
  4798  		setAddresses{"3", network.NewAddresses("10.0.3.1")},
  4799  		startAliveMachine{"3", ""},
  4800  		setMachineStatus{"3", status.Started, ""},
  4801  		setMachineInstanceStatus{"3", status.Started, "I am number three"},
  4802  
  4803  		addApplicationOffer{name: "hosted-mysql", applicationName: "mysql", owner: "admin", endpoints: []string{"server"}},
  4804  		addRemoteApplication{name: "remote-wordpress", charm: "wordpress", endpoints: []string{"db"}, isConsumerProxy: true},
  4805  		relateApplications{"remote-wordpress", "mysql", ""},
  4806  		addOfferConnection{sourceModelUUID: coretesting.ModelTag.Id(), name: "hosted-mysql", username: "fred", relationKey: "remote-wordpress:db mysql:server"},
  4807  	}
  4808  	for _, s := range steps {
  4809  		s.step(c, ctx)
  4810  	}
  4811  	return ctx
  4812  }
  4813  
  4814  func (s *StatusSuite) TestStatusWithFormatTabular(c *gc.C) {
  4815  	ctx := s.prepareTabularData(c)
  4816  	defer s.resetContext(c, ctx)
  4817  	code, stdout, stderr := runStatus(c, "--format", "tabular", "--relations")
  4818  	c.Check(code, gc.Equals, 0)
  4819  	c.Check(string(stderr), gc.Equals, "")
  4820  	expected := `
  4821  Model       Controller  Cloud/Region        Version  SLA          Timestamp       Notes
  4822  controller  kontroll    dummy/dummy-region  1.2.3    unsupported  15:04:05+07:00  upgrade available: 1.2.4
  4823  
  4824  SAAS         Status   Store  URL
  4825  hosted-riak  unknown  local  me/model.riak
  4826  
  4827  App        Version          Status       Scale  Charm      Store       Rev  OS      Notes
  4828  logging    a bit too lo...  error            2  logging    jujucharms    1  ubuntu  exposed
  4829  mysql      5.7.13           maintenance      1  mysql      jujucharms    1  ubuntu  exposed
  4830  wordpress  4.5.3            active           1  wordpress  jujucharms    3  ubuntu  exposed
  4831  
  4832  Unit          Workload     Agent  Machine  Public address  Ports  Message
  4833  mysql/0*      maintenance  idle   2        10.0.2.1               installing all the things
  4834    logging/1*  error        idle            10.0.2.1               somehow lost in all those logs
  4835  wordpress/0*  active       idle   1        10.0.1.1               
  4836    logging/0   active       idle            10.0.1.1               
  4837  
  4838  Machine  State    DNS       Inst id       Series   AZ          Message
  4839  0        started  10.0.0.1  controller-0  quantal  us-east-1a  
  4840  1        started  10.0.1.1  snowflake     quantal              
  4841  2        started  10.0.2.1  controller-2  quantal              
  4842  3        started  10.0.3.1  controller-3  quantal              I am number three
  4843  
  4844  Offer         Application  Charm  Rev  Connected  Endpoint  Interface  Role
  4845  hosted-mysql  mysql        mysql  1    1/1        server    mysql      provider
  4846  
  4847  Relation provider      Requirer                   Interface  Type         Message
  4848  mysql:juju-info        logging:info               juju-info  subordinate  
  4849  mysql:server           wordpress:db               mysql      regular      suspended  
  4850  wordpress:logging-dir  logging:logging-directory  logging    subordinate  
  4851  
  4852  `[1:]
  4853  
  4854  	output := substituteFakeTimestamp(c, stdout, false)
  4855  	output = substituteSpacingBetweenTimestampAndNotes(c, output)
  4856  	c.Assert(string(output), gc.Equals, expected)
  4857  }
  4858  
  4859  func (s *StatusSuite) TestStatusWithFormatYaml(c *gc.C) {
  4860  	ctx := s.prepareTabularData(c)
  4861  	defer s.resetContext(c, ctx)
  4862  	code, stdout, stderr := runStatus(c, "--format", "yaml")
  4863  	c.Check(code, gc.Equals, 0)
  4864  	c.Check(string(stderr), gc.Equals, "")
  4865  	c.Assert(string(stdout), jc.Contains, "display-name: snowflake")
  4866  }
  4867  
  4868  func (s *StatusSuite) TestStatusWithFormatJson(c *gc.C) {
  4869  	ctx := s.prepareTabularData(c)
  4870  	defer s.resetContext(c, ctx)
  4871  	code, stdout, stderr := runStatus(c, "--format", "json")
  4872  	c.Check(code, gc.Equals, 0)
  4873  	c.Check(string(stderr), gc.Equals, "")
  4874  	c.Assert(string(stdout), jc.Contains, `"display-name":"snowflake"`)
  4875  }
  4876  
  4877  func (s *StatusSuite) TestFormatTabularHookActionName(c *gc.C) {
  4878  	status := formattedStatus{
  4879  		Applications: map[string]applicationStatus{
  4880  			"foo": {
  4881  				Units: map[string]unitStatus{
  4882  					"foo/0": {
  4883  						JujuStatusInfo: statusInfoContents{
  4884  							Current: status.Executing,
  4885  							Message: "running config-changed hook",
  4886  						},
  4887  						WorkloadStatusInfo: statusInfoContents{
  4888  							Current: status.Maintenance,
  4889  							Message: "doing some work",
  4890  						},
  4891  					},
  4892  					"foo/1": {
  4893  						JujuStatusInfo: statusInfoContents{
  4894  							Current: status.Executing,
  4895  							Message: "running action backup database",
  4896  						},
  4897  						WorkloadStatusInfo: statusInfoContents{
  4898  							Current: status.Maintenance,
  4899  							Message: "doing some work",
  4900  						},
  4901  					},
  4902  				},
  4903  			},
  4904  		},
  4905  	}
  4906  	out := &bytes.Buffer{}
  4907  	err := FormatTabular(out, false, status)
  4908  	c.Assert(err, jc.ErrorIsNil)
  4909  	c.Assert(out.String(), gc.Equals, `
  4910  Model  Controller  Cloud/Region  Version
  4911                                   
  4912  
  4913  App  Version  Status  Scale  Charm  Store  Rev  OS  Notes
  4914  foo                       2                  0      
  4915  
  4916  Unit   Workload     Agent      Machine  Public address  Ports  Message
  4917  foo/0  maintenance  executing                                  (config-changed) doing some work
  4918  foo/1  maintenance  executing                                  (backup database) doing some work
  4919  `[1:])
  4920  }
  4921  
  4922  func (s *StatusSuite) TestFormatTabularCAASModel(c *gc.C) {
  4923  	status := formattedStatus{
  4924  		Model: modelStatus{
  4925  			Type: "caas",
  4926  		},
  4927  		Applications: map[string]applicationStatus{
  4928  			"foo": {
  4929  				Scale:   2,
  4930  				Address: "54.32.1.2",
  4931  				Units: map[string]unitStatus{
  4932  					"foo/0": {
  4933  						JujuStatusInfo: statusInfoContents{
  4934  							Current: status.Allocating,
  4935  						},
  4936  						WorkloadStatusInfo: statusInfoContents{
  4937  							Current: status.Active,
  4938  						},
  4939  					},
  4940  					"foo/1": {
  4941  						Address:     "10.0.0.1",
  4942  						OpenedPorts: []string{"80/TCP"},
  4943  						JujuStatusInfo: statusInfoContents{
  4944  							Current: status.Running,
  4945  						},
  4946  						WorkloadStatusInfo: statusInfoContents{
  4947  							Current: status.Active,
  4948  						},
  4949  					},
  4950  				},
  4951  			},
  4952  		},
  4953  	}
  4954  	out := &bytes.Buffer{}
  4955  	err := FormatTabular(out, false, status)
  4956  	c.Assert(err, jc.ErrorIsNil)
  4957  	c.Assert(out.String(), gc.Equals, `
  4958  Model  Controller  Cloud/Region  Version
  4959                                   
  4960  
  4961  App  Version  Status  Scale  Charm  Store  Rev  OS  Address    Notes
  4962  foo                     1/2                  0      54.32.1.2  
  4963  
  4964  Unit   Workload  Agent       Address   Ports   Message
  4965  foo/0  active    allocating                    
  4966  foo/1  active    running     10.0.0.1  80/TCP  
  4967  `[1:])
  4968  }
  4969  
  4970  func (s *StatusSuite) TestFormatTabularStatusNotes(c *gc.C) {
  4971  	fStatus := formattedStatus{
  4972  		Model: modelStatus{
  4973  			Type: "caas",
  4974  		},
  4975  		Applications: map[string]applicationStatus{
  4976  			"foo": {
  4977  				Scale:   1,
  4978  				Address: "54.32.1.2",
  4979  				StatusInfo: statusInfoContents{
  4980  					Message: "Error: ImagePullBackOff",
  4981  				},
  4982  				Units: map[string]unitStatus{
  4983  					"foo/0": {
  4984  						Address:     "10.0.0.1",
  4985  						OpenedPorts: []string{"80/TCP"},
  4986  						JujuStatusInfo: statusInfoContents{
  4987  							Current: status.Allocating,
  4988  						},
  4989  						WorkloadStatusInfo: statusInfoContents{
  4990  							Current: status.Waiting,
  4991  						},
  4992  					},
  4993  				},
  4994  			},
  4995  		},
  4996  	}
  4997  	out := &bytes.Buffer{}
  4998  	err := FormatTabular(out, false, fStatus)
  4999  	c.Assert(err, jc.ErrorIsNil)
  5000  	c.Assert(out.String(), gc.Equals, `
  5001  Model  Controller  Cloud/Region  Version
  5002                                   
  5003  
  5004  App  Version  Status  Scale  Charm  Store  Rev  OS  Address    Notes
  5005  foo                     0/1                  0      54.32.1.2  Error: ImagePullBackOff
  5006  
  5007  Unit   Workload  Agent       Address   Ports   Message
  5008  foo/0  waiting   allocating  10.0.0.1  80/TCP  
  5009  `[1:])
  5010  }
  5011  
  5012  func (s *StatusSuite) TestFormatTabularStatusNotesIAAS(c *gc.C) {
  5013  	status := formattedStatus{
  5014  		Applications: map[string]applicationStatus{
  5015  			"foo": {
  5016  				Address: "54.32.1.2",
  5017  				StatusInfo: statusInfoContents{
  5018  					Message: "Error: ImagePullBackOff",
  5019  				},
  5020  				Units: map[string]unitStatus{
  5021  					"foo/0": {
  5022  						Address:     "10.0.0.1",
  5023  						OpenedPorts: []string{"80/TCP"},
  5024  						JujuStatusInfo: statusInfoContents{
  5025  							Current: status.Idle,
  5026  						},
  5027  						WorkloadStatusInfo: statusInfoContents{
  5028  							Current: status.Waiting,
  5029  						},
  5030  					},
  5031  				},
  5032  			},
  5033  		},
  5034  	}
  5035  	out := &bytes.Buffer{}
  5036  	err := FormatTabular(out, false, status)
  5037  	c.Assert(err, jc.ErrorIsNil)
  5038  	c.Assert(out.String(), gc.Equals, `
  5039  Model  Controller  Cloud/Region  Version
  5040                                   
  5041  
  5042  App  Version  Status  Scale  Charm  Store  Rev  OS  Notes
  5043  foo                       1                  0      
  5044  
  5045  Unit   Workload  Agent  Machine  Public address  Ports   Message
  5046  foo/0  waiting   idle                            80/TCP  
  5047  `[1:])
  5048  }
  5049  
  5050  func (s *StatusSuite) TestStatusWithNilStatusAPI(c *gc.C) {
  5051  	ctx := s.newContext(c)
  5052  	defer s.resetContext(c, ctx)
  5053  	steps := []stepper{
  5054  		addMachine{machineId: "0", job: state.JobManageModel},
  5055  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  5056  		startAliveMachine{"0", ""},
  5057  		setMachineStatus{"0", status.Started, ""},
  5058  	}
  5059  
  5060  	for _, s := range steps {
  5061  		s.step(c, ctx)
  5062  	}
  5063  
  5064  	client := fakeAPIClient{}
  5065  	var status = client.Status
  5066  	s.PatchValue(&status, func(_ []string) (*params.FullStatus, error) {
  5067  		return nil, nil
  5068  	})
  5069  	s.PatchValue(&newAPIClientForStatus, func(_ *statusCommand) (statusAPI, error) {
  5070  		return &client, nil
  5071  	})
  5072  
  5073  	code, _, stderr := runStatus(c, "--format", "tabular")
  5074  	c.Check(code, gc.Equals, 1)
  5075  	c.Check(string(stderr), gc.Equals, "ERROR unable to obtain the current status\n")
  5076  }
  5077  
  5078  func (s *StatusSuite) TestFormatTabularMetering(c *gc.C) {
  5079  	status := formattedStatus{
  5080  		Applications: map[string]applicationStatus{
  5081  			"foo": {
  5082  				Units: map[string]unitStatus{
  5083  					"foo/0": {
  5084  						MeterStatus: &meterStatus{
  5085  							Color:   "strange",
  5086  							Message: "warning: stable strangelets",
  5087  						},
  5088  					},
  5089  					"foo/1": {
  5090  						MeterStatus: &meterStatus{
  5091  							Color:   "up",
  5092  							Message: "things are looking up",
  5093  						},
  5094  					},
  5095  				},
  5096  			},
  5097  		},
  5098  	}
  5099  	out := &bytes.Buffer{}
  5100  	err := FormatTabular(out, false, status)
  5101  	c.Assert(err, jc.ErrorIsNil)
  5102  	c.Assert(out.String(), gc.Equals, `
  5103  Model  Controller  Cloud/Region  Version
  5104                                   
  5105  
  5106  App  Version  Status  Scale  Charm  Store  Rev  OS  Notes
  5107  foo                     0/2                  0      
  5108  
  5109  Unit   Workload  Agent  Machine  Public address  Ports  Message
  5110  foo/0                                                   
  5111  foo/1                                                   
  5112  
  5113  Entity  Meter status  Message
  5114  foo/0   strange       warning: stable strangelets  
  5115  foo/1   up            things are looking up        
  5116  `[1:])
  5117  }
  5118  
  5119  //
  5120  // Filtering Feature
  5121  //
  5122  
  5123  func (s *StatusSuite) FilteringTestSetup(c *gc.C) *context {
  5124  	ctx := s.newContext(c)
  5125  
  5126  	steps := []stepper{
  5127  		// Given a machine is started
  5128  		// And the machine's ID is "0"
  5129  		// And the machine's job is to manage the environment
  5130  		addMachine{machineId: "0", job: state.JobManageModel},
  5131  		startAliveMachine{"0", ""},
  5132  		setMachineStatus{"0", status.Started, ""},
  5133  		// And the machine's address is "10.0.0.1"
  5134  		setAddresses{"0", network.NewAddresses("10.0.0.1")},
  5135  		// And a container is started
  5136  		// And the container's ID is "0/lxd/0"
  5137  		addContainer{"0", "0/lxd/0", state.JobHostUnits},
  5138  
  5139  		// And the "wordpress" charm is available
  5140  		addCharm{"wordpress"},
  5141  		addApplication{name: "wordpress", charm: "wordpress"},
  5142  		// And the "mysql" charm is available
  5143  		addCharm{"mysql"},
  5144  		addApplication{name: "mysql", charm: "mysql"},
  5145  		// And the "logging" charm is available
  5146  		addCharm{"logging"},
  5147  
  5148  		// And a machine is started
  5149  		// And the machine's ID is "1"
  5150  		// And the machine's job is to host units
  5151  		addMachine{machineId: "1", job: state.JobHostUnits},
  5152  		startAliveMachine{"1", ""},
  5153  		setMachineStatus{"1", status.Started, ""},
  5154  		// And the machine's address is "10.0.1.1"
  5155  		setAddresses{"1", network.NewAddresses("10.0.1.1")},
  5156  		// And a unit of "wordpress" is deployed to machine "1"
  5157  		addAliveUnit{"wordpress", "1"},
  5158  		// And the unit is started
  5159  		setAgentStatus{"wordpress/0", status.Idle, "", nil},
  5160  		setUnitStatus{"wordpress/0", status.Active, "", nil},
  5161  		// And a machine is started
  5162  
  5163  		// And the machine's ID is "2"
  5164  		// And the machine's job is to host units
  5165  		addMachine{machineId: "2", job: state.JobHostUnits},
  5166  		startAliveMachine{"2", ""},
  5167  		setMachineStatus{"2", status.Started, ""},
  5168  		// And the machine's address is "10.0.2.1"
  5169  		setAddresses{"2", network.NewAddresses("10.0.2.1")},
  5170  		// And a unit of "mysql" is deployed to machine "2"
  5171  		addAliveUnit{"mysql", "2"},
  5172  		// And the unit is started
  5173  		setAgentStatus{"mysql/0", status.Idle, "", nil},
  5174  		setUnitStatus{"mysql/0", status.Active, "", nil},
  5175  		// And the "logging" application is added
  5176  		addApplication{name: "logging", charm: "logging"},
  5177  		// And the application is exposed
  5178  		setApplicationExposed{"logging", true},
  5179  		// And the "wordpress" application is related to the "mysql" application
  5180  		relateApplications{"wordpress", "mysql", ""},
  5181  		// And the "wordpress" application is related to the "logging" application
  5182  		relateApplications{"wordpress", "logging", ""},
  5183  		// And the "mysql" application is related to the "logging" application
  5184  		relateApplications{"mysql", "logging", ""},
  5185  		// And the "logging" application is a subordinate to unit 0 of the "wordpress" application
  5186  		addSubordinate{"wordpress/0", "logging"},
  5187  		setAgentStatus{"logging/0", status.Idle, "", nil},
  5188  		setUnitStatus{"logging/0", status.Active, "", nil},
  5189  		// And the "logging" application is a subordinate to unit 0 of the "mysql" application
  5190  		addSubordinate{"mysql/0", "logging"},
  5191  		setAgentStatus{"logging/1", status.Idle, "", nil},
  5192  		setUnitStatus{"logging/1", status.Active, "", nil},
  5193  		setUnitsAlive{"logging"},
  5194  	}
  5195  
  5196  	ctx.run(c, steps)
  5197  	return ctx
  5198  }
  5199  
  5200  // Scenario: One unit is in an errored state and user filters to active
  5201  func (s *StatusSuite) TestFilterToActive(c *gc.C) {
  5202  	ctx := s.FilteringTestSetup(c)
  5203  	defer s.resetContext(c, ctx)
  5204  
  5205  	// Given unit 1 of the "logging" application has an error
  5206  	setAgentStatus{"logging/1", status.Error, "mock error", nil}.step(c, ctx)
  5207  	// And unit 0 of the "mysql" application has an error
  5208  	setAgentStatus{"mysql/0", status.Error, "mock error", nil}.step(c, ctx)
  5209  	// When I run juju status --format oneline started
  5210  	_, stdout, stderr := runStatus(c, "--format", "oneline", "active")
  5211  	c.Assert(string(stderr), gc.Equals, "")
  5212  	// Then I should receive output prefixed with:
  5213  	const expected = `
  5214  
  5215  - wordpress/0: 10.0.1.1 (agent:idle, workload:active)
  5216    - logging/0: 10.0.1.1 (agent:idle, workload:active)
  5217  `
  5218  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5219  }
  5220  
  5221  // Scenario: user filters to a single machine
  5222  func (s *StatusSuite) TestFilterToMachine(c *gc.C) {
  5223  	ctx := s.FilteringTestSetup(c)
  5224  	defer s.resetContext(c, ctx)
  5225  
  5226  	// When I run juju status --format oneline 1
  5227  	_, stdout, stderr := runStatus(c, "--format", "oneline", "1")
  5228  	c.Assert(string(stderr), gc.Equals, "")
  5229  	// Then I should receive output prefixed with:
  5230  	const expected = `
  5231  
  5232  - wordpress/0: 10.0.1.1 (agent:idle, workload:active)
  5233    - logging/0: 10.0.1.1 (agent:idle, workload:active)
  5234  `
  5235  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5236  }
  5237  
  5238  // Scenario: user filters to a machine, shows containers
  5239  func (s *StatusSuite) TestFilterToMachineShowsContainer(c *gc.C) {
  5240  	ctx := s.FilteringTestSetup(c)
  5241  	defer s.resetContext(c, ctx)
  5242  
  5243  	// When I run juju status --format yaml 0
  5244  	_, stdout, stderr := runStatus(c, "--format", "yaml", "0")
  5245  	c.Assert(string(stderr), gc.Equals, "")
  5246  	// Then I should receive output matching:
  5247  	const expected = "(.|\n)*machines:(.|\n)*\"0\"(.|\n)*0/lxd/0(.|\n)*"
  5248  	c.Assert(string(stdout), gc.Matches, expected)
  5249  }
  5250  
  5251  // Scenario: user filters to a container
  5252  func (s *StatusSuite) TestFilterToContainer(c *gc.C) {
  5253  	ctx := s.FilteringTestSetup(c)
  5254  	defer s.resetContext(c, ctx)
  5255  
  5256  	// When I run juju status --format yaml 0/lxd/0
  5257  	_, stdout, stderr := runStatus(c, "--format", "yaml", "0/lxd/0")
  5258  	c.Assert(string(stderr), gc.Equals, "")
  5259  
  5260  	const expected = "" +
  5261  		"model:\n" +
  5262  		"  name: controller\n" +
  5263  		"  type: iaas\n" +
  5264  		"  controller: kontroll\n" +
  5265  		"  cloud: dummy\n" +
  5266  		"  region: dummy-region\n" +
  5267  		"  version: 1.2.3\n" +
  5268  		"  model-status:\n" +
  5269  		"    current: available\n" +
  5270  		"    since: 01 Apr 15 01:23+10:00\n" +
  5271  		"  sla: unsupported\n" +
  5272  		"machines:\n" +
  5273  		"  \"0\":\n" +
  5274  		"    juju-status:\n" +
  5275  		"      current: started\n" +
  5276  		"      since: 01 Apr 15 01:23+10:00\n" +
  5277  		"    dns-name: 10.0.0.1\n" +
  5278  		"    ip-addresses:\n" +
  5279  		"    - 10.0.0.1\n" +
  5280  		"    instance-id: controller-0\n" +
  5281  		"    machine-status:\n" +
  5282  		"      current: pending\n" +
  5283  		"      since: 01 Apr 15 01:23+10:00\n" +
  5284  		"    series: quantal\n" +
  5285  		"    network-interfaces:\n" +
  5286  		"      eth0:\n" +
  5287  		"        ip-addresses:\n" +
  5288  		"        - 10.0.0.1\n" +
  5289  		"        mac-address: aa:bb:cc:dd:ee:ff\n" +
  5290  		"        is-up: true\n" +
  5291  		"    containers:\n" +
  5292  		"      0/lxd/0:\n" +
  5293  		"        juju-status:\n" +
  5294  		"          current: pending\n" +
  5295  		"          since: 01 Apr 15 01:23+10:00\n" +
  5296  		"        instance-id: pending\n" +
  5297  		"        machine-status:\n" +
  5298  		"          current: pending\n" +
  5299  		"          since: 01 Apr 15 01:23+10:00\n" +
  5300  		"        series: quantal\n" +
  5301  		"    hardware: arch=amd64 cores=1 mem=1024M root-disk=8192M\n" +
  5302  		"    controller-member-status: adding-vote\n" +
  5303  		"applications: {}\n" +
  5304  		"storage: {}\n" +
  5305  		"controller:\n" +
  5306  		"  timestamp: 15:04:05+07:00\n"
  5307  
  5308  	out := substituteFakeTime(c, "since", stdout, ctx.expectIsoTime)
  5309  	out = substituteFakeTimestamp(c, out, ctx.expectIsoTime)
  5310  	c.Assert(string(out), gc.Equals, expected)
  5311  }
  5312  
  5313  // Scenario: One unit is in an errored state and user filters to errored
  5314  func (s *StatusSuite) TestFilterToErrored(c *gc.C) {
  5315  	ctx := s.FilteringTestSetup(c)
  5316  	defer s.resetContext(c, ctx)
  5317  
  5318  	// Given unit 1 of the "logging" application has an error
  5319  	setAgentStatus{"logging/1", status.Error, "mock error", nil}.step(c, ctx)
  5320  	// When I run juju status --format oneline error
  5321  	_, stdout, stderr := runStatus(c, "--format", "oneline", "error")
  5322  	c.Assert(stderr, gc.IsNil)
  5323  	// Then I should receive output prefixed with:
  5324  	const expected = `
  5325  
  5326  - mysql/0: 10.0.2.1 (agent:idle, workload:active)
  5327    - logging/1: 10.0.2.1 (agent:idle, workload:error)
  5328  `
  5329  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5330  }
  5331  
  5332  // Scenario: User filters to mysql application
  5333  func (s *StatusSuite) TestFilterToApplication(c *gc.C) {
  5334  	ctx := s.FilteringTestSetup(c)
  5335  	defer s.resetContext(c, ctx)
  5336  
  5337  	// When I run juju status --format oneline error
  5338  	_, stdout, stderr := runStatus(c, "--format", "oneline", "mysql")
  5339  	c.Assert(stderr, gc.IsNil)
  5340  	// Then I should receive output prefixed with:
  5341  	const expected = `
  5342  
  5343  - mysql/0: 10.0.2.1 (agent:idle, workload:active)
  5344    - logging/1: 10.0.2.1 (agent:idle, workload:active)
  5345  `
  5346  
  5347  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5348  }
  5349  
  5350  // Scenario: User filters to exposed applications
  5351  func (s *StatusSuite) TestFilterToExposedApplication(c *gc.C) {
  5352  	ctx := s.FilteringTestSetup(c)
  5353  	defer s.resetContext(c, ctx)
  5354  
  5355  	// Given unit 1 of the "mysql" application is exposed
  5356  	setApplicationExposed{"mysql", true}.step(c, ctx)
  5357  	// And the logging application is not exposed
  5358  	setApplicationExposed{"logging", false}.step(c, ctx)
  5359  	// And the wordpress application is not exposed
  5360  	setApplicationExposed{"wordpress", false}.step(c, ctx)
  5361  	// When I run juju status --format oneline exposed
  5362  	_, stdout, stderr := runStatus(c, "--format", "oneline", "exposed")
  5363  	c.Assert(stderr, gc.IsNil)
  5364  	// Then I should receive output prefixed with:
  5365  	const expected = `
  5366  
  5367  - mysql/0: 10.0.2.1 (agent:idle, workload:active)
  5368    - logging/1: 10.0.2.1 (agent:idle, workload:active)
  5369  `
  5370  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5371  }
  5372  
  5373  // Scenario: User filters to non-exposed applications
  5374  func (s *StatusSuite) TestFilterToNotExposedApplication(c *gc.C) {
  5375  	ctx := s.FilteringTestSetup(c)
  5376  	defer s.resetContext(c, ctx)
  5377  
  5378  	setApplicationExposed{"mysql", true}.step(c, ctx)
  5379  	// When I run juju status --format oneline not exposed
  5380  	_, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed")
  5381  	c.Assert(stderr, gc.IsNil)
  5382  	// Then I should receive output prefixed with:
  5383  	const expected = `
  5384  
  5385  - wordpress/0: 10.0.1.1 (agent:idle, workload:active)
  5386    - logging/0: 10.0.1.1 (agent:idle, workload:active)
  5387  `
  5388  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5389  }
  5390  
  5391  // Scenario: Filtering on Subnets
  5392  func (s *StatusSuite) TestFilterOnSubnet(c *gc.C) {
  5393  	ctx := s.FilteringTestSetup(c)
  5394  	defer s.resetContext(c, ctx)
  5395  
  5396  	// Given the address for machine "1" is "localhost"
  5397  	setAddresses{"1", network.NewAddresses("localhost", "127.0.0.1")}.step(c, ctx)
  5398  	// And the address for machine "2" is "10.0.2.1"
  5399  	setAddresses{"2", network.NewAddresses("10.0.2.1")}.step(c, ctx)
  5400  	// When I run juju status --format oneline 127.0.0.1
  5401  	_, stdout, stderr := runStatus(c, "--format", "oneline", "127.0.0.1")
  5402  	c.Assert(stderr, gc.IsNil)
  5403  	// Then I should receive output prefixed with:
  5404  	const expected = `
  5405  
  5406  - wordpress/0: localhost (agent:idle, workload:active)
  5407    - logging/0: localhost (agent:idle, workload:active)
  5408  `
  5409  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5410  }
  5411  
  5412  // Scenario: Filtering on Ports
  5413  func (s *StatusSuite) TestFilterOnPorts(c *gc.C) {
  5414  	ctx := s.FilteringTestSetup(c)
  5415  	defer s.resetContext(c, ctx)
  5416  
  5417  	// Given the address for machine "1" is "localhost"
  5418  	setAddresses{"1", network.NewAddresses("localhost")}.step(c, ctx)
  5419  	// And the address for machine "2" is "10.0.2.1"
  5420  	setAddresses{"2", network.NewAddresses("10.0.2.1")}.step(c, ctx)
  5421  	openUnitPort{"wordpress/0", "tcp", 80}.step(c, ctx)
  5422  	// When I run juju status --format oneline 80/tcp
  5423  	_, stdout, stderr := runStatus(c, "--format", "oneline", "80/tcp")
  5424  	c.Assert(stderr, gc.IsNil)
  5425  	// Then I should receive output prefixed with:
  5426  	const expected = `
  5427  
  5428  - wordpress/0: localhost (agent:idle, workload:active) 80/tcp
  5429    - logging/0: localhost (agent:idle, workload:active)
  5430  `
  5431  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5432  }
  5433  
  5434  // Scenario: User filters out a parent, but not its subordinate
  5435  func (s *StatusSuite) TestFilterParentButNotSubordinate(c *gc.C) {
  5436  	ctx := s.FilteringTestSetup(c)
  5437  	defer s.resetContext(c, ctx)
  5438  
  5439  	// When I run juju status --format oneline 80/tcp
  5440  	_, stdout, stderr := runStatus(c, "--format", "oneline", "logging")
  5441  	c.Assert(stderr, gc.IsNil)
  5442  	// Then I should receive output prefixed with:
  5443  	const expected = `
  5444  
  5445  - mysql/0: 10.0.2.1 (agent:idle, workload:active)
  5446    - logging/1: 10.0.2.1 (agent:idle, workload:active)
  5447  - wordpress/0: 10.0.1.1 (agent:idle, workload:active)
  5448    - logging/0: 10.0.1.1 (agent:idle, workload:active)
  5449  `
  5450  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5451  }
  5452  
  5453  // Scenario: User filters out a subordinate, but not its parent
  5454  func (s *StatusSuite) TestFilterSubordinateButNotParent(c *gc.C) {
  5455  	ctx := s.FilteringTestSetup(c)
  5456  	defer s.resetContext(c, ctx)
  5457  
  5458  	// Given the wordpress application is exposed
  5459  	setApplicationExposed{"wordpress", true}.step(c, ctx)
  5460  	// When I run juju status --format oneline not exposed
  5461  	_, stdout, stderr := runStatus(c, "--format", "oneline", "not", "exposed")
  5462  	c.Assert(stderr, gc.IsNil)
  5463  	// Then I should receive output prefixed with:
  5464  	const expected = `
  5465  
  5466  - mysql/0: 10.0.2.1 (agent:idle, workload:active)
  5467    - logging/1: 10.0.2.1 (agent:idle, workload:active)
  5468  `
  5469  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5470  }
  5471  
  5472  func (s *StatusSuite) TestFilterMultipleHomogenousPatterns(c *gc.C) {
  5473  	ctx := s.FilteringTestSetup(c)
  5474  	defer s.resetContext(c, ctx)
  5475  
  5476  	_, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "mysql/0")
  5477  	c.Assert(stderr, gc.IsNil)
  5478  	// Then I should receive output prefixed with:
  5479  	const expected = `
  5480  
  5481  - mysql/0: 10.0.2.1 (agent:idle, workload:active)
  5482    - logging/1: 10.0.2.1 (agent:idle, workload:active)
  5483  - wordpress/0: 10.0.1.1 (agent:idle, workload:active)
  5484    - logging/0: 10.0.1.1 (agent:idle, workload:active)
  5485  `
  5486  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5487  }
  5488  
  5489  func (s *StatusSuite) TestFilterMultipleHeterogenousPatterns(c *gc.C) {
  5490  	ctx := s.FilteringTestSetup(c)
  5491  	defer s.resetContext(c, ctx)
  5492  
  5493  	_, stdout, stderr := runStatus(c, "--format", "oneline", "wordpress/0", "active")
  5494  	c.Assert(stderr, gc.IsNil)
  5495  	// Then I should receive output prefixed with:
  5496  	const expected = `
  5497  
  5498  - mysql/0: 10.0.2.1 (agent:idle, workload:active)
  5499    - logging/1: 10.0.2.1 (agent:idle, workload:active)
  5500  - wordpress/0: 10.0.1.1 (agent:idle, workload:active)
  5501    - logging/0: 10.0.1.1 (agent:idle, workload:active)
  5502  `
  5503  	c.Assert(string(stdout), gc.Equals, expected[1:])
  5504  }
  5505  
  5506  // TestSummaryStatusWithUnresolvableDns is result of bug# 1410320.
  5507  func (s *StatusSuite) TestSummaryStatusWithUnresolvableDns(c *gc.C) {
  5508  	formatter := &summaryFormatter{}
  5509  	formatter.resolveAndTrackIp("invalidDns")
  5510  	// Test should not panic.
  5511  }
  5512  
  5513  func initStatusCommand(args ...string) (*statusCommand, error) {
  5514  	com := &statusCommand{}
  5515  	return com, cmdtesting.InitCommand(modelcmd.Wrap(com), args)
  5516  }
  5517  
  5518  var statusInitTests = []struct {
  5519  	args    []string
  5520  	envVar  string
  5521  	isoTime bool
  5522  	err     string
  5523  }{
  5524  	{
  5525  		isoTime: false,
  5526  	}, {
  5527  		args:    []string{"--utc"},
  5528  		isoTime: true,
  5529  	}, {
  5530  		envVar:  "true",
  5531  		isoTime: true,
  5532  	}, {
  5533  		envVar: "foo",
  5534  		err:    "invalid JUJU_STATUS_ISO_TIME env var, expected true|false.*",
  5535  	},
  5536  }
  5537  
  5538  func (*StatusSuite) TestStatusCommandInit(c *gc.C) {
  5539  	defer os.Setenv(osenv.JujuStatusIsoTimeEnvKey, os.Getenv(osenv.JujuStatusIsoTimeEnvKey))
  5540  
  5541  	for i, t := range statusInitTests {
  5542  		c.Logf("test %d", i)
  5543  		os.Setenv(osenv.JujuStatusIsoTimeEnvKey, t.envVar)
  5544  		com, err := initStatusCommand(t.args...)
  5545  		if t.err != "" {
  5546  			c.Check(err, gc.ErrorMatches, t.err)
  5547  		} else {
  5548  			c.Check(err, jc.ErrorIsNil)
  5549  		}
  5550  		c.Check(com.isoTime, gc.DeepEquals, t.isoTime)
  5551  	}
  5552  }
  5553  
  5554  var statusTimeTest = test(
  5555  	"status generates timestamps as UTC in ISO format",
  5556  	addMachine{machineId: "0", job: state.JobManageModel},
  5557  	setAddresses{"0", network.NewAddresses("10.0.0.1")},
  5558  	startAliveMachine{"0", ""},
  5559  	setMachineStatus{"0", status.Started, ""},
  5560  	addCharm{"dummy"},
  5561  	addApplication{name: "dummy-application", charm: "dummy"},
  5562  
  5563  	addMachine{machineId: "1", job: state.JobHostUnits},
  5564  	startAliveMachine{"1", ""},
  5565  	setAddresses{"1", network.NewAddresses("10.0.1.1")},
  5566  	setMachineStatus{"1", status.Started, ""},
  5567  
  5568  	addAliveUnit{"dummy-application", "1"},
  5569  	expect{
  5570  		what: "add two units, one alive (in error state), one started",
  5571  		output: M{
  5572  			"model": M{
  5573  				"name":       "controller",
  5574  				"type":       "iaas",
  5575  				"controller": "kontroll",
  5576  				"cloud":      "dummy",
  5577  				"region":     "dummy-region",
  5578  				"version":    "1.2.3",
  5579  				"model-status": M{
  5580  					"current": "available",
  5581  					"since":   "01 Apr 15 01:23+10:00",
  5582  				},
  5583  				"sla": "unsupported",
  5584  			},
  5585  			"machines": M{
  5586  				"0": machine0,
  5587  				"1": machine1,
  5588  			},
  5589  			"applications": M{
  5590  				"dummy-application": dummyCharm(M{
  5591  					"application-status": M{
  5592  						"current": "waiting",
  5593  						"message": "waiting for machine",
  5594  						"since":   "01 Apr 15 01:23+10:00",
  5595  					},
  5596  					"units": M{
  5597  						"dummy-application/0": M{
  5598  							"machine": "1",
  5599  							"workload-status": M{
  5600  								"current": "waiting",
  5601  								"message": "waiting for machine",
  5602  								"since":   "01 Apr 15 01:23+10:00",
  5603  							},
  5604  							"juju-status": M{
  5605  								"current": "allocating",
  5606  								"since":   "01 Apr 15 01:23+10:00",
  5607  							},
  5608  							"public-address": "10.0.1.1",
  5609  						},
  5610  					},
  5611  				}),
  5612  			},
  5613  			"storage": M{},
  5614  			"controller": M{
  5615  				"timestamp": "15:04:05",
  5616  			},
  5617  		},
  5618  	},
  5619  )
  5620  
  5621  func (s *StatusSuite) TestIsoTimeFormat(c *gc.C) {
  5622  	func(t testCase) {
  5623  		// Prepare context and run all steps to setup.
  5624  		ctx := s.newContext(c)
  5625  		ctx.expectIsoTime = true
  5626  		defer s.resetContext(c, ctx)
  5627  		ctx.run(c, t.steps)
  5628  	}(statusTimeTest)
  5629  }
  5630  
  5631  func (s *StatusSuite) TestFormatProvisioningError(c *gc.C) {
  5632  	now := time.Now()
  5633  	status := &params.FullStatus{
  5634  		Model: params.ModelStatusInfo{
  5635  			CloudTag: "cloud-dummy",
  5636  		},
  5637  		Machines: map[string]params.MachineStatus{
  5638  			"1": {
  5639  				AgentStatus: params.DetailedStatus{
  5640  					Status: "error",
  5641  					Info:   "<error while provisioning>",
  5642  				},
  5643  				InstanceId:     "pending",
  5644  				InstanceStatus: params.DetailedStatus{},
  5645  				Series:         "trusty",
  5646  				Id:             "1",
  5647  				Jobs:           []multiwatcher.MachineJob{"JobHostUnits"},
  5648  			},
  5649  		},
  5650  		ControllerTimestamp: &now,
  5651  	}
  5652  	isoTime := true
  5653  	formatter := NewStatusFormatter(status, isoTime)
  5654  	formatted, err := formatter.format()
  5655  	c.Assert(err, jc.ErrorIsNil)
  5656  
  5657  	c.Check(formatted, jc.DeepEquals, formattedStatus{
  5658  		Model: modelStatus{
  5659  			Cloud: "dummy",
  5660  		},
  5661  		Machines: map[string]machineStatus{
  5662  			"1": {
  5663  				JujuStatus:        statusInfoContents{Current: "error", Message: "<error while provisioning>"},
  5664  				InstanceId:        "pending",
  5665  				Series:            "trusty",
  5666  				Id:                "1",
  5667  				Containers:        map[string]machineStatus{},
  5668  				NetworkInterfaces: map[string]networkInterface{},
  5669  				LXDProfiles:       map[string]lxdProfileContents{},
  5670  			},
  5671  		},
  5672  		Applications:       map[string]applicationStatus{},
  5673  		RemoteApplications: map[string]remoteApplicationStatus{},
  5674  		Offers:             map[string]offerStatus{},
  5675  		Controller: &controllerStatus{
  5676  			Timestamp: common.FormatTimeAsTimestamp(&now, isoTime),
  5677  		},
  5678  	})
  5679  }
  5680  
  5681  func (s *StatusSuite) TestMissingControllerTimestampInFullStatus(c *gc.C) {
  5682  	status := &params.FullStatus{
  5683  		Model: params.ModelStatusInfo{
  5684  			CloudTag: "cloud-dummy",
  5685  		},
  5686  		Machines: map[string]params.MachineStatus{
  5687  			"1": {
  5688  				AgentStatus: params.DetailedStatus{
  5689  					Status: "error",
  5690  					Info:   "<error while provisioning>",
  5691  				},
  5692  				InstanceId:     "pending",
  5693  				InstanceStatus: params.DetailedStatus{},
  5694  				Series:         "trusty",
  5695  				Id:             "1",
  5696  				Jobs:           []multiwatcher.MachineJob{"JobHostUnits"},
  5697  			},
  5698  		},
  5699  	}
  5700  	isoTime := true
  5701  	formatter := NewStatusFormatter(status, isoTime)
  5702  	formatted, err := formatter.format()
  5703  	c.Assert(err, jc.ErrorIsNil)
  5704  
  5705  	c.Check(formatted, jc.DeepEquals, formattedStatus{
  5706  		Model: modelStatus{
  5707  			Cloud: "dummy",
  5708  		},
  5709  		Machines: map[string]machineStatus{
  5710  			"1": {
  5711  				JujuStatus:        statusInfoContents{Current: "error", Message: "<error while provisioning>"},
  5712  				InstanceId:        "pending",
  5713  				Series:            "trusty",
  5714  				Id:                "1",
  5715  				Containers:        map[string]machineStatus{},
  5716  				NetworkInterfaces: map[string]networkInterface{},
  5717  				LXDProfiles:       map[string]lxdProfileContents{},
  5718  			},
  5719  		},
  5720  		Applications:       map[string]applicationStatus{},
  5721  		RemoteApplications: map[string]remoteApplicationStatus{},
  5722  		Offers:             map[string]offerStatus{},
  5723  	})
  5724  }
  5725  
  5726  func (s *StatusSuite) TestControllerTimestampInFullStatus(c *gc.C) {
  5727  	now := time.Now()
  5728  	status := &params.FullStatus{
  5729  		Model: params.ModelStatusInfo{
  5730  			CloudTag: "cloud-dummy",
  5731  		},
  5732  		Machines: map[string]params.MachineStatus{
  5733  			"1": {
  5734  				AgentStatus: params.DetailedStatus{
  5735  					Status: "error",
  5736  					Info:   "<error while provisioning>",
  5737  				},
  5738  				InstanceId:     "pending",
  5739  				InstanceStatus: params.DetailedStatus{},
  5740  				Series:         "trusty",
  5741  				Id:             "1",
  5742  				Jobs:           []multiwatcher.MachineJob{"JobHostUnits"},
  5743  			},
  5744  		},
  5745  		ControllerTimestamp: &now,
  5746  	}
  5747  	isoTime := true
  5748  	formatter := NewStatusFormatter(status, isoTime)
  5749  	formatted, err := formatter.format()
  5750  	c.Assert(err, jc.ErrorIsNil)
  5751  
  5752  	c.Check(formatted, jc.DeepEquals, formattedStatus{
  5753  		Model: modelStatus{
  5754  			Cloud: "dummy",
  5755  		},
  5756  		Machines: map[string]machineStatus{
  5757  			"1": {
  5758  				JujuStatus:        statusInfoContents{Current: "error", Message: "<error while provisioning>"},
  5759  				InstanceId:        "pending",
  5760  				Series:            "trusty",
  5761  				Id:                "1",
  5762  				Containers:        map[string]machineStatus{},
  5763  				NetworkInterfaces: map[string]networkInterface{},
  5764  				LXDProfiles:       map[string]lxdProfileContents{},
  5765  			},
  5766  		},
  5767  		Applications:       map[string]applicationStatus{},
  5768  		RemoteApplications: map[string]remoteApplicationStatus{},
  5769  		Offers:             map[string]offerStatus{},
  5770  		Controller: &controllerStatus{
  5771  			Timestamp: common.FormatTimeAsTimestamp(&now, isoTime),
  5772  		},
  5773  	})
  5774  }
  5775  
  5776  func (s *StatusSuite) TestTabularNoRelations(c *gc.C) {
  5777  	ctx := s.FilteringTestSetup(c)
  5778  	defer s.resetContext(c, ctx)
  5779  
  5780  	_, stdout, stderr := runStatus(c)
  5781  	c.Assert(stderr, gc.IsNil)
  5782  	c.Assert(strings.Contains(string(stdout), "Relation provider"), jc.IsFalse)
  5783  }
  5784  
  5785  func (s *StatusSuite) TestTabularDisplayRelations(c *gc.C) {
  5786  	ctx := s.FilteringTestSetup(c)
  5787  	defer s.resetContext(c, ctx)
  5788  
  5789  	_, stdout, stderr := runStatus(c, "--relations")
  5790  	c.Assert(stderr, gc.IsNil)
  5791  	c.Assert(strings.Contains(string(stdout), "Relation provider"), jc.IsTrue)
  5792  }
  5793  
  5794  func (s *StatusSuite) TestNonTabularDisplayRelations(c *gc.C) {
  5795  	ctx := s.FilteringTestSetup(c)
  5796  	defer s.resetContext(c, ctx)
  5797  
  5798  	_, stdout, stderr := runStatus(c, "--format=yaml", "--relations")
  5799  	c.Assert(string(stderr), gc.Equals, "provided relations option is always enabled in non tabular formats\n")
  5800  	logger.Criticalf("stdout -> \n%q", stdout)
  5801  	c.Assert(strings.Contains(string(stdout), "    relations:"), jc.IsTrue)
  5802  	c.Assert(strings.Contains(string(stdout), "storage:"), jc.IsTrue)
  5803  }
  5804  
  5805  func (s *StatusSuite) TestNonTabularDisplayStorage(c *gc.C) {
  5806  	ctx := s.FilteringTestSetup(c)
  5807  	defer s.resetContext(c, ctx)
  5808  
  5809  	_, stdout, stderr := runStatus(c, "--format=yaml", "--storage")
  5810  	c.Assert(string(stderr), gc.Equals, "provided storage option is always enabled in non tabular formats\n")
  5811  	c.Assert(strings.Contains(string(stdout), "    relations:"), jc.IsTrue)
  5812  	c.Assert(strings.Contains(string(stdout), "storage:"), jc.IsTrue)
  5813  }
  5814  
  5815  func (s *StatusSuite) TestNonTabularDisplayRelationsAndStorage(c *gc.C) {
  5816  	ctx := s.FilteringTestSetup(c)
  5817  	defer s.resetContext(c, ctx)
  5818  
  5819  	_, stdout, stderr := runStatus(c, "--format=yaml", "--relations", "--storage")
  5820  	c.Assert(string(stderr), gc.Equals, "provided relations, storage options are always enabled in non tabular formats\n")
  5821  	c.Assert(strings.Contains(string(stdout), "    relations:"), jc.IsTrue)
  5822  	c.Assert(strings.Contains(string(stdout), "storage:"), jc.IsTrue)
  5823  }
  5824  
  5825  func (s *StatusSuite) TestNonTabularRelations(c *gc.C) {
  5826  	ctx := s.FilteringTestSetup(c)
  5827  	defer s.resetContext(c, ctx)
  5828  
  5829  	_, stdout, stderr := runStatus(c, "--format=yaml")
  5830  	c.Assert(stderr, gc.IsNil)
  5831  	c.Assert(strings.Contains(string(stdout), "    relations:"), jc.IsTrue)
  5832  	c.Assert(strings.Contains(string(stdout), "storage:"), jc.IsTrue)
  5833  }
  5834  
  5835  func (s *StatusSuite) TestStatusFormatTabularEmptyModel(c *gc.C) {
  5836  	code, stdout, stderr := runStatus(c)
  5837  	c.Check(code, gc.Equals, 0)
  5838  	c.Check(string(stderr), gc.Equals, "Model \"controller\" is empty.\n")
  5839  	expected := `
  5840  Model       Controller  Cloud/Region        Version  SLA          Timestamp
  5841  controller  kontroll    dummy/dummy-region  1.2.3    unsupported  15:04:05+07:00
  5842  
  5843  `[1:]
  5844  	output := substituteFakeTimestamp(c, stdout, false)
  5845  	c.Assert(string(output), gc.Equals, expected)
  5846  }
  5847  
  5848  func (s *StatusSuite) TestStatusFormatTabularForUnmatchedFilter(c *gc.C) {
  5849  	code, stdout, stderr := runStatus(c, "unmatched")
  5850  	c.Check(code, gc.Equals, 0)
  5851  	c.Check(string(stderr), gc.Equals, "Nothing matched specified filter.\n")
  5852  	expected := `
  5853  Model       Controller  Cloud/Region        Version  SLA          Timestamp
  5854  controller  kontroll    dummy/dummy-region  1.2.3    unsupported  15:04:05+07:00
  5855  
  5856  `[1:]
  5857  	output := substituteFakeTimestamp(c, stdout, false)
  5858  	c.Assert(string(output), gc.Equals, expected)
  5859  
  5860  	_, _, stderr = runStatus(c, "cannot", "match", "me")
  5861  	c.Check(string(stderr), gc.Equals, "Nothing matched specified filters.\n")
  5862  }