github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/migrationmaster/client_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package migrationmaster_test
     5  
     6  import (
     7  	"encoding/json"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/url"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/httprequest"
    17  	jujutesting "github.com/juju/testing"
    18  	jc "github.com/juju/testing/checkers"
    19  	"github.com/juju/utils"
    20  	"github.com/juju/version"
    21  	gc "gopkg.in/check.v1"
    22  	charmresource "gopkg.in/juju/charm.v6/resource"
    23  	"gopkg.in/juju/names.v2"
    24  	"gopkg.in/macaroon.v2-unstable"
    25  
    26  	"github.com/juju/juju/api/base"
    27  	apitesting "github.com/juju/juju/api/base/testing"
    28  	"github.com/juju/juju/api/migrationmaster"
    29  	macapitesting "github.com/juju/juju/api/testing"
    30  	"github.com/juju/juju/apiserver/params"
    31  	"github.com/juju/juju/core/migration"
    32  	"github.com/juju/juju/core/watcher"
    33  	"github.com/juju/juju/resource"
    34  )
    35  
    36  type ClientSuite struct {
    37  	jujutesting.IsolationSuite
    38  }
    39  
    40  var _ = gc.Suite(&ClientSuite{})
    41  
    42  func (s *ClientSuite) TestWatch(c *gc.C) {
    43  	var stub jujutesting.Stub
    44  	apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
    45  		stub.AddCall(objType+"."+request, id, arg)
    46  		*(result.(*params.NotifyWatchResult)) = params.NotifyWatchResult{
    47  			NotifyWatcherId: "123",
    48  		}
    49  		return nil
    50  	})
    51  	expectWatch := &struct{ watcher.NotifyWatcher }{}
    52  	newWatcher := func(caller base.APICaller, result params.NotifyWatchResult) watcher.NotifyWatcher {
    53  		c.Check(caller, gc.NotNil)
    54  		c.Check(result, jc.DeepEquals, params.NotifyWatchResult{NotifyWatcherId: "123"})
    55  		return expectWatch
    56  	}
    57  	client := migrationmaster.NewClient(apiCaller, newWatcher)
    58  	w, err := client.Watch()
    59  	c.Check(err, jc.ErrorIsNil)
    60  	c.Check(w, gc.Equals, expectWatch)
    61  	stub.CheckCalls(c, []jujutesting.StubCall{{"MigrationMaster.Watch", []interface{}{"", nil}}})
    62  }
    63  
    64  func (s *ClientSuite) TestWatchCallError(c *gc.C) {
    65  	apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
    66  		return errors.New("boom")
    67  	})
    68  	client := migrationmaster.NewClient(apiCaller, nil)
    69  	_, err := client.Watch()
    70  	c.Assert(err, gc.ErrorMatches, "boom")
    71  }
    72  
    73  func (s *ClientSuite) TestMigrationStatus(c *gc.C) {
    74  	mac, err := macaroon.New([]byte("secret"), []byte("id"), "location")
    75  	c.Assert(err, jc.ErrorIsNil)
    76  	macs := []macaroon.Slice{{mac}}
    77  	macsJSON, err := json.Marshal(macs)
    78  	c.Assert(err, jc.ErrorIsNil)
    79  
    80  	modelUUID := utils.MustNewUUID().String()
    81  	controllerUUID := utils.MustNewUUID().String()
    82  	controllerTag := names.NewControllerTag(controllerUUID)
    83  	timestamp := time.Date(2016, 6, 22, 16, 42, 44, 0, time.UTC)
    84  	apiCaller := apitesting.APICallerFunc(func(_ string, _ int, _, _ string, _, result interface{}) error {
    85  		out := result.(*params.MasterMigrationStatus)
    86  		*out = params.MasterMigrationStatus{
    87  			Spec: params.MigrationSpec{
    88  				ModelTag: names.NewModelTag(modelUUID).String(),
    89  				TargetInfo: params.MigrationTargetInfo{
    90  					ControllerTag: controllerTag.String(),
    91  					Addrs:         []string{"2.2.2.2:2"},
    92  					CACert:        "cert",
    93  					AuthTag:       names.NewUserTag("admin").String(),
    94  					Password:      "secret",
    95  					Macaroons:     string(macsJSON),
    96  				},
    97  			},
    98  			MigrationId:      "id",
    99  			Phase:            "IMPORT",
   100  			PhaseChangedTime: timestamp,
   101  		}
   102  		return nil
   103  	})
   104  	client := migrationmaster.NewClient(apiCaller, nil)
   105  	status, err := client.MigrationStatus()
   106  	c.Assert(err, jc.ErrorIsNil)
   107  	// Extract macaroons so we can compare them separately
   108  	// (as they can't be compared using DeepEquals due to 'UnmarshaledAs')
   109  	statusMacs := status.TargetInfo.Macaroons
   110  	status.TargetInfo.Macaroons = nil
   111  	macapitesting.MacaroonEquals(c, statusMacs[0][0], mac)
   112  	c.Assert(status, gc.DeepEquals, migration.MigrationStatus{
   113  		MigrationId:      "id",
   114  		ModelUUID:        modelUUID,
   115  		Phase:            migration.IMPORT,
   116  		PhaseChangedTime: timestamp,
   117  		TargetInfo: migration.TargetInfo{
   118  			ControllerTag: controllerTag,
   119  			Addrs:         []string{"2.2.2.2:2"},
   120  			CACert:        "cert",
   121  			AuthTag:       names.NewUserTag("admin"),
   122  			Password:      "secret",
   123  		},
   124  	})
   125  }
   126  
   127  func (s *ClientSuite) TestSetPhase(c *gc.C) {
   128  	var stub jujutesting.Stub
   129  	apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   130  		stub.AddCall(objType+"."+request, id, arg)
   131  		return nil
   132  	})
   133  	client := migrationmaster.NewClient(apiCaller, nil)
   134  	err := client.SetPhase(migration.QUIESCE)
   135  	c.Assert(err, jc.ErrorIsNil)
   136  	expectedArg := params.SetMigrationPhaseArgs{Phase: "QUIESCE"}
   137  	stub.CheckCalls(c, []jujutesting.StubCall{
   138  		{"MigrationMaster.SetPhase", []interface{}{"", expectedArg}},
   139  	})
   140  }
   141  
   142  func (s *ClientSuite) TestSetPhaseError(c *gc.C) {
   143  	apiCaller := apitesting.APICallerFunc(func(string, int, string, string, interface{}, interface{}) error {
   144  		return errors.New("boom")
   145  	})
   146  	client := migrationmaster.NewClient(apiCaller, nil)
   147  	err := client.SetPhase(migration.QUIESCE)
   148  	c.Assert(err, gc.ErrorMatches, "boom")
   149  }
   150  
   151  func (s *ClientSuite) TestSetStatusMessage(c *gc.C) {
   152  	var stub jujutesting.Stub
   153  	apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   154  		stub.AddCall(objType+"."+request, id, arg)
   155  		return nil
   156  	})
   157  	client := migrationmaster.NewClient(apiCaller, nil)
   158  	err := client.SetStatusMessage("foo")
   159  	c.Assert(err, jc.ErrorIsNil)
   160  	expectedArg := params.SetMigrationStatusMessageArgs{Message: "foo"}
   161  	stub.CheckCalls(c, []jujutesting.StubCall{
   162  		{"MigrationMaster.SetStatusMessage", []interface{}{"", expectedArg}},
   163  	})
   164  }
   165  
   166  func (s *ClientSuite) TestSetStatusMessageError(c *gc.C) {
   167  	apiCaller := apitesting.APICallerFunc(func(string, int, string, string, interface{}, interface{}) error {
   168  		return errors.New("boom")
   169  	})
   170  	client := migrationmaster.NewClient(apiCaller, nil)
   171  	err := client.SetStatusMessage("foo")
   172  	c.Assert(err, gc.ErrorMatches, "boom")
   173  }
   174  
   175  func (s *ClientSuite) TestModelInfo(c *gc.C) {
   176  	var stub jujutesting.Stub
   177  	owner := names.NewUserTag("owner")
   178  	apiCaller := apitesting.APICallerFunc(func(objType string, v int, id, request string, arg, result interface{}) error {
   179  		stub.AddCall(objType+"."+request, id, arg)
   180  		*(result.(*params.MigrationModelInfo)) = params.MigrationModelInfo{
   181  			UUID:                   "uuid",
   182  			Name:                   "name",
   183  			OwnerTag:               owner.String(),
   184  			AgentVersion:           version.MustParse("1.2.3"),
   185  			ControllerAgentVersion: version.MustParse("1.2.4"),
   186  		}
   187  		return nil
   188  	})
   189  	client := migrationmaster.NewClient(apiCaller, nil)
   190  	model, err := client.ModelInfo()
   191  	stub.CheckCalls(c, []jujutesting.StubCall{
   192  		{"MigrationMaster.ModelInfo", []interface{}{"", nil}},
   193  	})
   194  	c.Check(err, jc.ErrorIsNil)
   195  	c.Check(model, jc.DeepEquals, migration.ModelInfo{
   196  		UUID:                   "uuid",
   197  		Name:                   "name",
   198  		Owner:                  owner,
   199  		AgentVersion:           version.MustParse("1.2.3"),
   200  		ControllerAgentVersion: version.MustParse("1.2.4"),
   201  	})
   202  }
   203  
   204  func (s *ClientSuite) TestPrechecks(c *gc.C) {
   205  	var stub jujutesting.Stub
   206  	apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   207  		stub.AddCall(objType+"."+request, id, arg)
   208  		return errors.New("blam")
   209  	})
   210  	client := migrationmaster.NewClient(apiCaller, nil)
   211  	err := client.Prechecks()
   212  	c.Check(err, gc.ErrorMatches, "blam")
   213  	stub.CheckCalls(c, []jujutesting.StubCall{
   214  		{"MigrationMaster.Prechecks", []interface{}{"", nil}},
   215  	})
   216  }
   217  
   218  func (s *ClientSuite) TestExport(c *gc.C) {
   219  	var stub jujutesting.Stub
   220  
   221  	fpHash := charmresource.NewFingerprintHash()
   222  	appFp := fpHash.Fingerprint()
   223  	unitFp := fpHash.Fingerprint()
   224  
   225  	appTs := time.Now()
   226  	unitTs := appTs.Add(time.Hour)
   227  
   228  	apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   229  		stub.AddCall(objType+"."+request, id, arg)
   230  		out := result.(*params.SerializedModel)
   231  		*out = params.SerializedModel{
   232  			Bytes:  []byte("foo"),
   233  			Charms: []string{"cs:foo-1"},
   234  			Tools: []params.SerializedModelTools{{
   235  				Version: "2.0.0-trusty-amd64",
   236  				URI:     "/tools/0",
   237  			}},
   238  			Resources: []params.SerializedModelResource{{
   239  				Application: "fooapp",
   240  				Name:        "bin",
   241  				ApplicationRevision: params.SerializedModelResourceRevision{
   242  					Revision:       2,
   243  					Type:           "file",
   244  					Path:           "bin.tar.gz",
   245  					Description:    "who knows",
   246  					Origin:         "upload",
   247  					FingerprintHex: appFp.Hex(),
   248  					Size:           123,
   249  					Timestamp:      appTs,
   250  					Username:       "bob",
   251  				},
   252  				CharmStoreRevision: params.SerializedModelResourceRevision{
   253  					// Imitate a placeholder for the test by having no Timestamp
   254  					// and an empty Fingerpritn
   255  					Revision:    3,
   256  					Type:        "file",
   257  					Path:        "fink.tar.gz",
   258  					Description: "knows who",
   259  					Origin:      "store",
   260  					Size:        321,
   261  					Username:    "xena",
   262  				},
   263  				UnitRevisions: map[string]params.SerializedModelResourceRevision{
   264  					"fooapp/0": {
   265  						Revision:       1,
   266  						Type:           "file",
   267  						Path:           "blink.tar.gz",
   268  						Description:    "bo knows",
   269  						Origin:         "store",
   270  						FingerprintHex: unitFp.Hex(),
   271  						Size:           222,
   272  						Timestamp:      unitTs,
   273  						Username:       "bambam",
   274  					},
   275  				},
   276  			}},
   277  		}
   278  		return nil
   279  	})
   280  	client := migrationmaster.NewClient(apiCaller, nil)
   281  	out, err := client.Export()
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	stub.CheckCalls(c, []jujutesting.StubCall{
   284  		{"MigrationMaster.Export", []interface{}{"", nil}},
   285  	})
   286  	c.Assert(out, gc.DeepEquals, migration.SerializedModel{
   287  		Bytes:  []byte("foo"),
   288  		Charms: []string{"cs:foo-1"},
   289  		Tools: map[version.Binary]string{
   290  			version.MustParseBinary("2.0.0-trusty-amd64"): "/tools/0",
   291  		},
   292  		Resources: []migration.SerializedModelResource{{
   293  			ApplicationRevision: resource.Resource{
   294  				Resource: charmresource.Resource{
   295  					Meta: charmresource.Meta{
   296  						Name:        "bin",
   297  						Type:        charmresource.TypeFile,
   298  						Path:        "bin.tar.gz",
   299  						Description: "who knows",
   300  					},
   301  					Origin:      charmresource.OriginUpload,
   302  					Revision:    2,
   303  					Fingerprint: appFp,
   304  					Size:        123,
   305  				},
   306  				ApplicationID: "fooapp",
   307  				Username:      "bob",
   308  				Timestamp:     appTs,
   309  			},
   310  			CharmStoreRevision: resource.Resource{
   311  				Resource: charmresource.Resource{
   312  					Meta: charmresource.Meta{
   313  						Name:        "bin",
   314  						Type:        charmresource.TypeFile,
   315  						Path:        "fink.tar.gz",
   316  						Description: "knows who",
   317  					},
   318  					Origin:   charmresource.OriginStore,
   319  					Revision: 3,
   320  					Size:     321,
   321  				},
   322  				ApplicationID: "fooapp",
   323  				Username:      "xena",
   324  			},
   325  			UnitRevisions: map[string]resource.Resource{
   326  				"fooapp/0": {
   327  					Resource: charmresource.Resource{
   328  						Meta: charmresource.Meta{
   329  							Name:        "bin",
   330  							Type:        charmresource.TypeFile,
   331  							Path:        "blink.tar.gz",
   332  							Description: "bo knows",
   333  						},
   334  						Origin:      charmresource.OriginStore,
   335  						Revision:    1,
   336  						Fingerprint: unitFp,
   337  						Size:        222,
   338  					},
   339  					ApplicationID: "fooapp",
   340  					Username:      "bambam",
   341  					Timestamp:     unitTs,
   342  				},
   343  			},
   344  		}},
   345  	})
   346  }
   347  
   348  func (s *ClientSuite) TestExportError(c *gc.C) {
   349  	apiCaller := apitesting.APICallerFunc(func(string, int, string, string, interface{}, interface{}) error {
   350  		return errors.New("blam")
   351  	})
   352  	client := migrationmaster.NewClient(apiCaller, nil)
   353  	_, err := client.Export()
   354  	c.Assert(err, gc.ErrorMatches, "blam")
   355  }
   356  
   357  const resourceContent = "resourceful"
   358  
   359  func setupFakeHTTP() (*migrationmaster.Client, *fakeDoer) {
   360  	doer := &fakeDoer{
   361  		response: &http.Response{
   362  			StatusCode: 200,
   363  			Body:       ioutil.NopCloser(strings.NewReader(resourceContent)),
   364  		},
   365  	}
   366  	caller := &fakeHTTPCaller{
   367  		httpClient: &httprequest.Client{
   368  			Doer: doer,
   369  		},
   370  	}
   371  	return migrationmaster.NewClient(caller, nil), doer
   372  }
   373  
   374  func (s *ClientSuite) TestOpenResource(c *gc.C) {
   375  	client, doer := setupFakeHTTP()
   376  	r, err := client.OpenResource("app", "blob")
   377  	c.Assert(err, jc.ErrorIsNil)
   378  	checkReader(c, r, "resourceful")
   379  	c.Check(doer.method, gc.Equals, "GET")
   380  	c.Check(doer.url, gc.Equals, "/applications/app/resources/blob")
   381  }
   382  
   383  func (s *ClientSuite) TestReap(c *gc.C) {
   384  	var stub jujutesting.Stub
   385  	apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   386  		stub.AddCall(objType+"."+request, id, arg)
   387  		return nil
   388  	})
   389  	client := migrationmaster.NewClient(apiCaller, nil)
   390  	err := client.Reap()
   391  	c.Check(err, jc.ErrorIsNil)
   392  	stub.CheckCalls(c, []jujutesting.StubCall{
   393  		{"MigrationMaster.Reap", []interface{}{"", nil}},
   394  	})
   395  }
   396  
   397  func (s *ClientSuite) TestReapError(c *gc.C) {
   398  	apiCaller := apitesting.APICallerFunc(func(string, int, string, string, interface{}, interface{}) error {
   399  		return errors.New("blam")
   400  	})
   401  	client := migrationmaster.NewClient(apiCaller, nil)
   402  	err := client.Reap()
   403  	c.Assert(err, gc.ErrorMatches, "blam")
   404  }
   405  
   406  func (s *ClientSuite) TestWatchMinionReports(c *gc.C) {
   407  	var stub jujutesting.Stub
   408  	apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   409  		stub.AddCall(objType+"."+request, id, arg)
   410  		*(result.(*params.NotifyWatchResult)) = params.NotifyWatchResult{
   411  			NotifyWatcherId: "123",
   412  		}
   413  		return nil
   414  	})
   415  
   416  	expectWatch := &struct{ watcher.NotifyWatcher }{}
   417  	newWatcher := func(caller base.APICaller, result params.NotifyWatchResult) watcher.NotifyWatcher {
   418  		c.Check(caller, gc.NotNil)
   419  		c.Check(result, jc.DeepEquals, params.NotifyWatchResult{NotifyWatcherId: "123"})
   420  		return expectWatch
   421  	}
   422  	client := migrationmaster.NewClient(apiCaller, newWatcher)
   423  	w, err := client.WatchMinionReports()
   424  	c.Check(err, jc.ErrorIsNil)
   425  	c.Check(w, gc.Equals, expectWatch)
   426  	stub.CheckCalls(c, []jujutesting.StubCall{{"MigrationMaster.WatchMinionReports", []interface{}{"", nil}}})
   427  }
   428  
   429  func (s *ClientSuite) TestWatchMinionReportsError(c *gc.C) {
   430  	apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   431  		return errors.New("boom")
   432  	})
   433  	client := migrationmaster.NewClient(apiCaller, nil)
   434  	_, err := client.WatchMinionReports()
   435  	c.Assert(err, gc.ErrorMatches, "boom")
   436  }
   437  
   438  func (s *ClientSuite) TestMinionReports(c *gc.C) {
   439  	var stub jujutesting.Stub
   440  	apiCaller := apitesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
   441  		stub.AddCall(objType+"."+request, id, arg)
   442  		out := result.(*params.MinionReports)
   443  		*out = params.MinionReports{
   444  			MigrationId:  "id",
   445  			Phase:        "IMPORT",
   446  			SuccessCount: 4,
   447  			UnknownCount: 3,
   448  			UnknownSample: []string{
   449  				names.NewMachineTag("3").String(),
   450  				names.NewMachineTag("4").String(),
   451  				names.NewUnitTag("foo/0").String(),
   452  				names.NewApplicationTag("bar").String(),
   453  			},
   454  			Failed: []string{
   455  				names.NewMachineTag("5").String(),
   456  				names.NewUnitTag("foo/1").String(),
   457  				names.NewUnitTag("foo/2").String(),
   458  				names.NewApplicationTag("foobar").String(),
   459  			},
   460  		}
   461  		return nil
   462  	})
   463  	client := migrationmaster.NewClient(apiCaller, nil)
   464  	out, err := client.MinionReports()
   465  	c.Assert(err, jc.ErrorIsNil)
   466  	stub.CheckCalls(c, []jujutesting.StubCall{
   467  		{"MigrationMaster.MinionReports", []interface{}{"", nil}},
   468  	})
   469  	c.Assert(out, gc.DeepEquals, migration.MinionReports{
   470  		MigrationId:             "id",
   471  		Phase:                   migration.IMPORT,
   472  		SuccessCount:            4,
   473  		UnknownCount:            3,
   474  		SomeUnknownMachines:     []string{"3", "4"},
   475  		SomeUnknownUnits:        []string{"foo/0"},
   476  		SomeUnknownApplications: []string{"bar"},
   477  		FailedMachines:          []string{"5"},
   478  		FailedUnits:             []string{"foo/1", "foo/2"},
   479  		FailedApplications:      []string{"foobar"},
   480  	})
   481  }
   482  
   483  func (s *ClientSuite) TestMinionReportsFailedCall(c *gc.C) {
   484  	apiCaller := apitesting.APICallerFunc(func(string, int, string, string, interface{}, interface{}) error {
   485  		return errors.New("blam")
   486  	})
   487  	client := migrationmaster.NewClient(apiCaller, nil)
   488  	_, err := client.MinionReports()
   489  	c.Assert(err, gc.ErrorMatches, "blam")
   490  }
   491  
   492  func (s *ClientSuite) TestMinionReportsInvalidPhase(c *gc.C) {
   493  	apiCaller := apitesting.APICallerFunc(func(_ string, _ int, _ string, _ string, _ interface{}, result interface{}) error {
   494  		out := result.(*params.MinionReports)
   495  		*out = params.MinionReports{
   496  			Phase: "BLARGH",
   497  		}
   498  		return nil
   499  	})
   500  	client := migrationmaster.NewClient(apiCaller, nil)
   501  	_, err := client.MinionReports()
   502  	c.Assert(err, gc.ErrorMatches, `invalid phase: "BLARGH"`)
   503  }
   504  
   505  func (s *ClientSuite) TestMinionReportsBadUnknownTag(c *gc.C) {
   506  	apiCaller := apitesting.APICallerFunc(func(_ string, _ int, _ string, _ string, _ interface{}, result interface{}) error {
   507  		out := result.(*params.MinionReports)
   508  		*out = params.MinionReports{
   509  			Phase:         "IMPORT",
   510  			UnknownSample: []string{"carl"},
   511  		}
   512  		return nil
   513  	})
   514  	client := migrationmaster.NewClient(apiCaller, nil)
   515  	_, err := client.MinionReports()
   516  	c.Assert(err, gc.ErrorMatches, `processing unknown agents: "carl" is not a valid tag`)
   517  }
   518  
   519  func (s *ClientSuite) TestMinionReportsBadFailedTag(c *gc.C) {
   520  	apiCaller := apitesting.APICallerFunc(func(_ string, _ int, _ string, _ string, _ interface{}, result interface{}) error {
   521  		out := result.(*params.MinionReports)
   522  		*out = params.MinionReports{
   523  			Phase:  "IMPORT",
   524  			Failed: []string{"dave"},
   525  		}
   526  		return nil
   527  	})
   528  	client := migrationmaster.NewClient(apiCaller, nil)
   529  	_, err := client.MinionReports()
   530  	c.Assert(err, gc.ErrorMatches, `processing failed agents: "dave" is not a valid tag`)
   531  }
   532  
   533  func (s *ClientSuite) TestStreamModelLogs(c *gc.C) {
   534  	caller := fakeConnector{path: new(string), attrs: &url.Values{}}
   535  	client := migrationmaster.NewClient(caller, nil)
   536  	stream, err := client.StreamModelLog(time.Date(2016, 12, 2, 10, 24, 1, 1000000, time.UTC))
   537  	c.Assert(stream, gc.IsNil)
   538  	c.Assert(err, gc.ErrorMatches, "colonel abrams")
   539  
   540  	c.Assert(*caller.path, gc.Equals, "/log")
   541  	c.Assert(*caller.attrs, gc.DeepEquals, url.Values{
   542  		"replay":        {"true"},
   543  		"noTail":        {"true"},
   544  		"startTime":     {"2016-12-02T10:24:01.001Z"},
   545  		"includeEntity": nil,
   546  		"includeModule": nil,
   547  		"excludeEntity": nil,
   548  		"excludeModule": nil,
   549  	})
   550  }
   551  
   552  type fakeConnector struct {
   553  	base.APICaller
   554  
   555  	path  *string
   556  	attrs *url.Values
   557  }
   558  
   559  func (fakeConnector) BestFacadeVersion(string) int {
   560  	return 0
   561  }
   562  
   563  func (c fakeConnector) ConnectStream(path string, attrs url.Values) (base.Stream, error) {
   564  	*c.path = path
   565  	*c.attrs = attrs
   566  	return nil, errors.New("colonel abrams")
   567  }
   568  
   569  type fakeHTTPCaller struct {
   570  	base.APICaller
   571  	httpClient *httprequest.Client
   572  	err        error
   573  }
   574  
   575  func (fakeHTTPCaller) BestFacadeVersion(string) int {
   576  	return 0
   577  }
   578  
   579  func (c fakeHTTPCaller) HTTPClient() (*httprequest.Client, error) {
   580  	return c.httpClient, c.err
   581  }
   582  
   583  type fakeDoer struct {
   584  	response *http.Response
   585  	method   string
   586  	url      string
   587  }
   588  
   589  func (d *fakeDoer) Do(req *http.Request) (*http.Response, error) {
   590  	d.method = req.Method
   591  	d.url = req.URL.String()
   592  	return d.response, nil
   593  }
   594  
   595  func checkReader(c *gc.C, r io.Reader, expected string) {
   596  	actual, err := ioutil.ReadAll(r)
   597  	c.Assert(err, jc.ErrorIsNil)
   598  	c.Check(string(actual), gc.Equals, expected)
   599  }