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 }