github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/apiserver/client/client_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package client_test 5 6 import ( 7 "fmt" 8 "net/url" 9 "strconv" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/juju/errors" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/utils" 17 gc "launchpad.net/gocheck" 18 19 "github.com/juju/juju/charm" 20 charmtesting "github.com/juju/juju/charm/testing" 21 "github.com/juju/juju/constraints" 22 "github.com/juju/juju/environs" 23 "github.com/juju/juju/environs/config" 24 "github.com/juju/juju/environs/manual" 25 envstorage "github.com/juju/juju/environs/storage" 26 toolstesting "github.com/juju/juju/environs/tools/testing" 27 "github.com/juju/juju/instance" 28 "github.com/juju/juju/provider/dummy" 29 "github.com/juju/juju/state" 30 "github.com/juju/juju/state/api" 31 "github.com/juju/juju/state/api/params" 32 "github.com/juju/juju/state/apiserver/client" 33 "github.com/juju/juju/state/presence" 34 coretesting "github.com/juju/juju/testing" 35 "github.com/juju/juju/version" 36 ) 37 38 type clientSuite struct { 39 baseSuite 40 } 41 42 var _ = gc.Suite(&clientSuite{}) 43 44 func (s *clientSuite) TestClientStatus(c *gc.C) { 45 s.setUpScenario(c) 46 status, err := s.APIState.Client().Status(nil) 47 c.Assert(err, gc.IsNil) 48 c.Assert(status, jc.DeepEquals, scenarioStatus) 49 } 50 51 func (s *clientSuite) TestCompatibleSettingsParsing(c *gc.C) { 52 // Test the exported settings parsing in a compatible way. 53 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 54 service, err := s.State.Service("dummy") 55 c.Assert(err, gc.IsNil) 56 ch, _, err := service.Charm() 57 c.Assert(err, gc.IsNil) 58 c.Assert(ch.URL().String(), gc.Equals, "local:quantal/dummy-1") 59 60 // Empty string will be returned as nil. 61 options := map[string]string{ 62 "title": "foobar", 63 "username": "", 64 } 65 settings, err := client.ParseSettingsCompatible(ch, options) 66 c.Assert(err, gc.IsNil) 67 c.Assert(settings, gc.DeepEquals, charm.Settings{ 68 "title": "foobar", 69 "username": nil, 70 }) 71 72 // Illegal settings lead to an error. 73 options = map[string]string{ 74 "yummy": "didgeridoo", 75 } 76 settings, err = client.ParseSettingsCompatible(ch, options) 77 c.Assert(err, gc.ErrorMatches, `unknown option "yummy"`) 78 } 79 80 func (s *clientSuite) TestClientServiceSet(c *gc.C) { 81 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 82 83 err := s.APIState.Client().ServiceSet("dummy", map[string]string{ 84 "title": "foobar", 85 "username": "user name", 86 }) 87 c.Assert(err, gc.IsNil) 88 settings, err := dummy.ConfigSettings() 89 c.Assert(err, gc.IsNil) 90 c.Assert(settings, gc.DeepEquals, charm.Settings{ 91 "title": "foobar", 92 "username": "user name", 93 }) 94 95 err = s.APIState.Client().ServiceSet("dummy", map[string]string{ 96 "title": "barfoo", 97 "username": "", 98 }) 99 c.Assert(err, gc.IsNil) 100 settings, err = dummy.ConfigSettings() 101 c.Assert(err, gc.IsNil) 102 c.Assert(settings, gc.DeepEquals, charm.Settings{ 103 "title": "barfoo", 104 "username": "", 105 }) 106 } 107 108 func (s *clientSuite) TestClientServerUnset(c *gc.C) { 109 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 110 111 err := s.APIState.Client().ServiceSet("dummy", map[string]string{ 112 "title": "foobar", 113 "username": "user name", 114 }) 115 c.Assert(err, gc.IsNil) 116 settings, err := dummy.ConfigSettings() 117 c.Assert(err, gc.IsNil) 118 c.Assert(settings, gc.DeepEquals, charm.Settings{ 119 "title": "foobar", 120 "username": "user name", 121 }) 122 123 err = s.APIState.Client().ServiceUnset("dummy", []string{"username"}) 124 c.Assert(err, gc.IsNil) 125 settings, err = dummy.ConfigSettings() 126 c.Assert(err, gc.IsNil) 127 c.Assert(settings, gc.DeepEquals, charm.Settings{ 128 "title": "foobar", 129 }) 130 } 131 132 func (s *clientSuite) TestClientServiceSetYAML(c *gc.C) { 133 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 134 135 err := s.APIState.Client().ServiceSetYAML("dummy", "dummy:\n title: foobar\n username: user name\n") 136 c.Assert(err, gc.IsNil) 137 settings, err := dummy.ConfigSettings() 138 c.Assert(err, gc.IsNil) 139 c.Assert(settings, gc.DeepEquals, charm.Settings{ 140 "title": "foobar", 141 "username": "user name", 142 }) 143 144 err = s.APIState.Client().ServiceSetYAML("dummy", "dummy:\n title: barfoo\n username: \n") 145 c.Assert(err, gc.IsNil) 146 settings, err = dummy.ConfigSettings() 147 c.Assert(err, gc.IsNil) 148 c.Assert(settings, gc.DeepEquals, charm.Settings{ 149 "title": "barfoo", 150 }) 151 } 152 153 var clientAddServiceUnitsTests = []struct { 154 about string 155 service string // if not set, defaults to 'dummy' 156 expected []string 157 to string 158 err string 159 }{ 160 { 161 about: "returns unit names", 162 expected: []string{"dummy/0", "dummy/1", "dummy/2"}, 163 }, 164 { 165 about: "fails trying to add zero units", 166 err: "must add at least one unit", 167 }, 168 { 169 about: "cannot mix to when adding multiple units", 170 err: "cannot use NumUnits with ToMachineSpec", 171 expected: []string{"dummy/0", "dummy/1"}, 172 to: "0", 173 }, 174 { 175 // Note: chained-state, we add 1 unit here, but the 3 units 176 // from the first condition still exist 177 about: "force the unit onto bootstrap machine", 178 expected: []string{"dummy/3"}, 179 to: "0", 180 }, 181 { 182 about: "unknown service name", 183 service: "unknown-service", 184 err: `service "unknown-service" not found`, 185 }, 186 } 187 188 func (s *clientSuite) TestClientAddServiceUnits(c *gc.C) { 189 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 190 for i, t := range clientAddServiceUnitsTests { 191 c.Logf("test %d. %s", i, t.about) 192 serviceName := t.service 193 if serviceName == "" { 194 serviceName = "dummy" 195 } 196 units, err := s.APIState.Client().AddServiceUnits(serviceName, len(t.expected), t.to) 197 if t.err != "" { 198 c.Assert(err, gc.ErrorMatches, t.err) 199 continue 200 } 201 c.Assert(err, gc.IsNil) 202 c.Assert(units, gc.DeepEquals, t.expected) 203 } 204 // Test that we actually assigned the unit to machine 0 205 forcedUnit, err := s.BackingState.Unit("dummy/3") 206 c.Assert(err, gc.IsNil) 207 assignedMachine, err := forcedUnit.AssignedMachineId() 208 c.Assert(err, gc.IsNil) 209 c.Assert(assignedMachine, gc.Equals, "0") 210 } 211 212 var clientCharmInfoTests = []struct { 213 about string 214 url string 215 err string 216 }{ 217 { 218 about: "retrieves charm info", 219 url: "local:quantal/wordpress-3", 220 }, 221 { 222 about: "invalid URL", 223 url: "not-valid", 224 err: "charm url series is not resolved", 225 }, 226 { 227 about: "invalid schema", 228 url: "not-valid:your-arguments", 229 err: `charm URL has invalid schema: "not-valid:your-arguments"`, 230 }, 231 { 232 about: "unknown charm", 233 url: "cs:missing/one-1", 234 err: `charm "cs:missing/one-1" not found`, 235 }, 236 } 237 238 func (s *clientSuite) TestClientCharmInfo(c *gc.C) { 239 // Use wordpress for tests so that we can compare Provides and Requires. 240 charm := s.AddTestingCharm(c, "wordpress") 241 for i, t := range clientCharmInfoTests { 242 c.Logf("test %d. %s", i, t.about) 243 info, err := s.APIState.Client().CharmInfo(t.url) 244 if t.err != "" { 245 c.Assert(err, gc.ErrorMatches, t.err) 246 continue 247 } 248 c.Assert(err, gc.IsNil) 249 expected := &api.CharmInfo{ 250 Revision: charm.Revision(), 251 URL: charm.URL().String(), 252 Config: charm.Config(), 253 Meta: charm.Meta(), 254 } 255 c.Assert(info, gc.DeepEquals, expected) 256 } 257 } 258 259 func (s *clientSuite) TestClientEnvironmentInfo(c *gc.C) { 260 conf, _ := s.State.EnvironConfig() 261 info, err := s.APIState.Client().EnvironmentInfo() 262 c.Assert(err, gc.IsNil) 263 env, err := s.State.Environment() 264 c.Assert(err, gc.IsNil) 265 c.Assert(info.DefaultSeries, gc.Equals, config.PreferredSeries(conf)) 266 c.Assert(info.ProviderType, gc.Equals, conf.Type()) 267 c.Assert(info.Name, gc.Equals, conf.Name()) 268 c.Assert(info.UUID, gc.Equals, env.UUID()) 269 } 270 271 var clientAnnotationsTests = []struct { 272 about string 273 initial map[string]string 274 input map[string]string 275 expected map[string]string 276 err string 277 }{ 278 { 279 about: "test setting an annotation", 280 input: map[string]string{"mykey": "myvalue"}, 281 expected: map[string]string{"mykey": "myvalue"}, 282 }, 283 { 284 about: "test setting multiple annotations", 285 input: map[string]string{"key1": "value1", "key2": "value2"}, 286 expected: map[string]string{"key1": "value1", "key2": "value2"}, 287 }, 288 { 289 about: "test overriding annotations", 290 initial: map[string]string{"mykey": "myvalue"}, 291 input: map[string]string{"mykey": "another-value"}, 292 expected: map[string]string{"mykey": "another-value"}, 293 }, 294 { 295 about: "test setting an invalid annotation", 296 input: map[string]string{"invalid.key": "myvalue"}, 297 err: `cannot update annotations on .*: invalid key "invalid.key"`, 298 }, 299 } 300 301 func (s *clientSuite) TestClientAnnotations(c *gc.C) { 302 // Set up entities. 303 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 304 unit, err := service.AddUnit() 305 c.Assert(err, gc.IsNil) 306 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 307 c.Assert(err, gc.IsNil) 308 environment, err := s.State.Environment() 309 c.Assert(err, gc.IsNil) 310 type taggedAnnotator interface { 311 state.Annotator 312 state.Entity 313 } 314 entities := []taggedAnnotator{service, unit, machine, environment} 315 for i, t := range clientAnnotationsTests { 316 for _, entity := range entities { 317 id := entity.Tag() 318 c.Logf("test %d. %s. entity %s", i, t.about, id) 319 // Set initial entity annotations. 320 err := entity.SetAnnotations(t.initial) 321 c.Assert(err, gc.IsNil) 322 // Add annotations using the API call. 323 err = s.APIState.Client().SetAnnotations(id, t.input) 324 if t.err != "" { 325 c.Assert(err, gc.ErrorMatches, t.err) 326 continue 327 } 328 // Check annotations are correctly set. 329 dbann, err := entity.Annotations() 330 c.Assert(err, gc.IsNil) 331 c.Assert(dbann, gc.DeepEquals, t.expected) 332 // Retrieve annotations using the API call. 333 ann, err := s.APIState.Client().GetAnnotations(id) 334 c.Assert(err, gc.IsNil) 335 // Check annotations are correctly returned. 336 c.Assert(ann, gc.DeepEquals, dbann) 337 // Clean up annotations on the current entity. 338 cleanup := make(map[string]string) 339 for key := range dbann { 340 cleanup[key] = "" 341 } 342 err = entity.SetAnnotations(cleanup) 343 c.Assert(err, gc.IsNil) 344 } 345 } 346 } 347 348 func (s *clientSuite) TestClientAnnotationsBadEntity(c *gc.C) { 349 bad := []string{"", "machine", "-foo", "foo-", "---", "machine-jim", "unit-123", "unit-foo", "service-", "service-foo/bar"} 350 expected := `".*" is not a valid( [a-z]+)? tag` 351 for _, id := range bad { 352 err := s.APIState.Client().SetAnnotations(id, map[string]string{"mykey": "myvalue"}) 353 c.Assert(err, gc.ErrorMatches, expected) 354 _, err = s.APIState.Client().GetAnnotations(id) 355 c.Assert(err, gc.ErrorMatches, expected) 356 } 357 } 358 359 var serviceExposeTests = []struct { 360 about string 361 service string 362 err string 363 exposed bool 364 }{ 365 { 366 about: "unknown service name", 367 service: "unknown-service", 368 err: `service "unknown-service" not found`, 369 }, 370 { 371 about: "expose a service", 372 service: "dummy-service", 373 exposed: true, 374 }, 375 { 376 about: "expose an already exposed service", 377 service: "exposed-service", 378 exposed: true, 379 }, 380 } 381 382 func (s *clientSuite) TestClientServiceExpose(c *gc.C) { 383 charm := s.AddTestingCharm(c, "dummy") 384 serviceNames := []string{"dummy-service", "exposed-service"} 385 svcs := make([]*state.Service, len(serviceNames)) 386 var err error 387 for i, name := range serviceNames { 388 svcs[i] = s.AddTestingService(c, name, charm) 389 c.Assert(svcs[i].IsExposed(), gc.Equals, false) 390 } 391 err = svcs[1].SetExposed() 392 c.Assert(err, gc.IsNil) 393 c.Assert(svcs[1].IsExposed(), gc.Equals, true) 394 for i, t := range serviceExposeTests { 395 c.Logf("test %d. %s", i, t.about) 396 err = s.APIState.Client().ServiceExpose(t.service) 397 if t.err != "" { 398 c.Assert(err, gc.ErrorMatches, t.err) 399 } else { 400 c.Assert(err, gc.IsNil) 401 service, err := s.State.Service(t.service) 402 c.Assert(err, gc.IsNil) 403 c.Assert(service.IsExposed(), gc.Equals, t.exposed) 404 } 405 } 406 } 407 408 var serviceUnexposeTests = []struct { 409 about string 410 service string 411 err string 412 initial bool 413 expected bool 414 }{ 415 { 416 about: "unknown service name", 417 service: "unknown-service", 418 err: `service "unknown-service" not found`, 419 }, 420 { 421 about: "unexpose a service", 422 service: "dummy-service", 423 initial: true, 424 expected: false, 425 }, 426 { 427 about: "unexpose an already unexposed service", 428 service: "dummy-service", 429 initial: false, 430 expected: false, 431 }, 432 } 433 434 func (s *clientSuite) TestClientServiceUnexpose(c *gc.C) { 435 charm := s.AddTestingCharm(c, "dummy") 436 for i, t := range serviceUnexposeTests { 437 c.Logf("test %d. %s", i, t.about) 438 svc := s.AddTestingService(c, "dummy-service", charm) 439 if t.initial { 440 svc.SetExposed() 441 } 442 c.Assert(svc.IsExposed(), gc.Equals, t.initial) 443 err := s.APIState.Client().ServiceUnexpose(t.service) 444 if t.err == "" { 445 c.Assert(err, gc.IsNil) 446 svc.Refresh() 447 c.Assert(svc.IsExposed(), gc.Equals, t.expected) 448 } else { 449 c.Assert(err, gc.ErrorMatches, t.err) 450 } 451 err = svc.Destroy() 452 c.Assert(err, gc.IsNil) 453 } 454 } 455 456 var serviceDestroyTests = []struct { 457 about string 458 service string 459 err string 460 }{ 461 { 462 about: "unknown service name", 463 service: "unknown-service", 464 err: `service "unknown-service" not found`, 465 }, 466 { 467 about: "destroy a service", 468 service: "dummy-service", 469 }, 470 { 471 about: "destroy an already destroyed service", 472 service: "dummy-service", 473 err: `service "dummy-service" not found`, 474 }, 475 } 476 477 func (s *clientSuite) TestClientServiceDestroy(c *gc.C) { 478 s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy")) 479 for i, t := range serviceDestroyTests { 480 c.Logf("test %d. %s", i, t.about) 481 err := s.APIState.Client().ServiceDestroy(t.service) 482 if t.err != "" { 483 c.Assert(err, gc.ErrorMatches, t.err) 484 } else { 485 c.Assert(err, gc.IsNil) 486 } 487 } 488 489 // Now do ServiceDestroy on a service with units. Destroy will 490 // cause the service to be not-Alive, but will not remove its 491 // document. 492 s.setUpScenario(c) 493 serviceName := "wordpress" 494 service, err := s.State.Service(serviceName) 495 c.Assert(err, gc.IsNil) 496 err = s.APIState.Client().ServiceDestroy(serviceName) 497 c.Assert(err, gc.IsNil) 498 err = service.Refresh() 499 c.Assert(err, gc.IsNil) 500 c.Assert(service.Life(), gc.Not(gc.Equals), state.Alive) 501 } 502 503 func assertLife(c *gc.C, entity state.Living, life state.Life) { 504 err := entity.Refresh() 505 c.Assert(err, gc.IsNil) 506 c.Assert(entity.Life(), gc.Equals, life) 507 } 508 509 func assertRemoved(c *gc.C, entity state.Living) { 510 err := entity.Refresh() 511 c.Assert(err, jc.Satisfies, errors.IsNotFound) 512 } 513 514 func (s *clientSuite) setupDestroyMachinesTest(c *gc.C) (*state.Machine, *state.Machine, *state.Machine, *state.Unit) { 515 m0, err := s.State.AddMachine("quantal", state.JobManageEnviron) 516 c.Assert(err, gc.IsNil) 517 m1, err := s.State.AddMachine("quantal", state.JobHostUnits) 518 c.Assert(err, gc.IsNil) 519 m2, err := s.State.AddMachine("quantal", state.JobHostUnits) 520 c.Assert(err, gc.IsNil) 521 522 sch := s.AddTestingCharm(c, "wordpress") 523 wordpress := s.AddTestingService(c, "wordpress", sch) 524 u, err := wordpress.AddUnit() 525 c.Assert(err, gc.IsNil) 526 err = u.AssignToMachine(m1) 527 c.Assert(err, gc.IsNil) 528 529 return m0, m1, m2, u 530 } 531 532 func (s *clientSuite) TestDestroyMachines(c *gc.C) { 533 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 534 535 err := s.APIState.Client().DestroyMachines("0", "1", "2") 536 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment; machine 1 has unit "wordpress/0" assigned`) 537 assertLife(c, m0, state.Alive) 538 assertLife(c, m1, state.Alive) 539 assertLife(c, m2, state.Dying) 540 541 err = u.UnassignFromMachine() 542 c.Assert(err, gc.IsNil) 543 err = s.APIState.Client().DestroyMachines("0", "1", "2") 544 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment`) 545 assertLife(c, m0, state.Alive) 546 assertLife(c, m1, state.Dying) 547 assertLife(c, m2, state.Dying) 548 } 549 550 func (s *clientSuite) TestForceDestroyMachines(c *gc.C) { 551 m0, m1, m2, u := s.setupDestroyMachinesTest(c) 552 553 err := s.APIState.Client().ForceDestroyMachines("0", "1", "2") 554 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 is required by the environment`) 555 assertLife(c, m0, state.Alive) 556 assertLife(c, m1, state.Alive) 557 assertLife(c, m2, state.Alive) 558 assertLife(c, u, state.Alive) 559 560 err = s.State.Cleanup() 561 c.Assert(err, gc.IsNil) 562 assertLife(c, m0, state.Alive) 563 assertLife(c, m1, state.Dead) 564 assertLife(c, m2, state.Dead) 565 assertRemoved(c, u) 566 } 567 568 func (s *clientSuite) TestDestroyPrincipalUnits(c *gc.C) { 569 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 570 units := make([]*state.Unit, 5) 571 for i := range units { 572 unit, err := wordpress.AddUnit() 573 c.Assert(err, gc.IsNil) 574 err = unit.SetStatus(params.StatusStarted, "", nil) 575 c.Assert(err, gc.IsNil) 576 units[i] = unit 577 } 578 579 // Destroy 2 of them; check they become Dying. 580 err := s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1") 581 c.Assert(err, gc.IsNil) 582 assertLife(c, units[0], state.Dying) 583 assertLife(c, units[1], state.Dying) 584 585 // Try to destroy an Alive one and a Dying one; check 586 // it destroys the Alive one and ignores the Dying one. 587 err = s.APIState.Client().DestroyServiceUnits("wordpress/2", "wordpress/0") 588 c.Assert(err, gc.IsNil) 589 assertLife(c, units[2], state.Dying) 590 591 // Try to destroy an Alive one along with a nonexistent one; check that 592 // the valid instruction is followed but the invalid one is warned about. 593 err = s.APIState.Client().DestroyServiceUnits("boojum/123", "wordpress/3") 594 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`) 595 assertLife(c, units[3], state.Dying) 596 597 // Make one Dead, and destroy an Alive one alongside it; check no errors. 598 wp0, err := s.State.Unit("wordpress/0") 599 c.Assert(err, gc.IsNil) 600 err = wp0.EnsureDead() 601 c.Assert(err, gc.IsNil) 602 err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/4") 603 c.Assert(err, gc.IsNil) 604 assertLife(c, units[0], state.Dead) 605 assertLife(c, units[4], state.Dying) 606 } 607 608 func (s *clientSuite) TestDestroySubordinateUnits(c *gc.C) { 609 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 610 wordpress0, err := wordpress.AddUnit() 611 c.Assert(err, gc.IsNil) 612 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 613 eps, err := s.State.InferEndpoints([]string{"logging", "wordpress"}) 614 c.Assert(err, gc.IsNil) 615 rel, err := s.State.AddRelation(eps...) 616 c.Assert(err, gc.IsNil) 617 ru, err := rel.Unit(wordpress0) 618 c.Assert(err, gc.IsNil) 619 err = ru.EnterScope(nil) 620 c.Assert(err, gc.IsNil) 621 logging0, err := s.State.Unit("logging/0") 622 c.Assert(err, gc.IsNil) 623 624 // Try to destroy the subordinate alone; check it fails. 625 err = s.APIState.Client().DestroyServiceUnits("logging/0") 626 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) 627 assertLife(c, logging0, state.Alive) 628 629 // Try to destroy the principal and the subordinate together; check it warns 630 // about the subordinate, but destroys the one it can. (The principal unit 631 // agent will be resposible for destroying the subordinate.) 632 err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "logging/0") 633 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`) 634 assertLife(c, wordpress0, state.Dying) 635 assertLife(c, logging0, state.Alive) 636 } 637 638 func (s *clientSuite) testClientUnitResolved(c *gc.C, retry bool, expectedResolvedMode state.ResolvedMode) { 639 // Setup: 640 s.setUpScenario(c) 641 u, err := s.State.Unit("wordpress/0") 642 c.Assert(err, gc.IsNil) 643 err = u.SetStatus(params.StatusError, "gaaah", nil) 644 c.Assert(err, gc.IsNil) 645 // Code under test: 646 err = s.APIState.Client().Resolved("wordpress/0", retry) 647 c.Assert(err, gc.IsNil) 648 // Freshen the unit's state. 649 err = u.Refresh() 650 c.Assert(err, gc.IsNil) 651 // And now the actual test assertions: we set the unit as resolved via 652 // the API so it should have a resolved mode set. 653 mode := u.Resolved() 654 c.Assert(mode, gc.Equals, expectedResolvedMode) 655 } 656 657 func (s *clientSuite) TestClientUnitResolved(c *gc.C) { 658 s.testClientUnitResolved(c, false, state.ResolvedNoHooks) 659 } 660 661 func (s *clientSuite) TestClientUnitResolvedRetry(c *gc.C) { 662 s.testClientUnitResolved(c, true, state.ResolvedRetryHooks) 663 } 664 665 func (s *clientSuite) TestClientServiceDeployCharmErrors(c *gc.C) { 666 _, restore := makeMockCharmStore() 667 defer restore() 668 for url, expect := range map[string]string{ 669 "wordpress": "charm url series is not resolved", 670 "cs:wordpress": "charm url series is not resolved", 671 "cs:precise/wordpress": "charm url must include revision", 672 "cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`, 673 } { 674 c.Logf("test %s", url) 675 err := s.APIState.Client().ServiceDeploy( 676 url, "service", 1, "", constraints.Value{}, "", 677 ) 678 c.Check(err, gc.ErrorMatches, expect) 679 _, err = s.State.Service("service") 680 c.Assert(err, jc.Satisfies, errors.IsNotFound) 681 } 682 } 683 684 func (s *clientSuite) TestClientServiceDeployWithNetworks(c *gc.C) { 685 store, restore := makeMockCharmStore() 686 defer restore() 687 curl, bundle := addCharm(c, store, "dummy") 688 cons := constraints.MustParse("mem=4G networks=^net3") 689 690 // Check for invalid network tags handling. 691 err := s.APIState.Client().ServiceDeployWithNetworks( 692 curl.String(), "service", 3, "", cons, "", 693 []string{"net1", "net2"}, 694 ) 695 c.Assert(err, gc.ErrorMatches, `"net1" is not a valid network tag`) 696 697 err = s.APIState.Client().ServiceDeployWithNetworks( 698 curl.String(), "service", 3, "", cons, "", 699 []string{"network-net1", "network-net2"}, 700 ) 701 c.Assert(err, gc.IsNil) 702 service := s.assertPrincipalDeployed(c, "service", curl, false, bundle, cons) 703 704 networks, err := service.Networks() 705 c.Assert(err, gc.IsNil) 706 c.Assert(networks, gc.DeepEquals, []string{"net1", "net2"}) 707 serviceCons, err := service.Constraints() 708 c.Assert(err, gc.IsNil) 709 c.Assert(serviceCons, gc.DeepEquals, cons) 710 } 711 712 func (s *clientSuite) assertPrincipalDeployed(c *gc.C, serviceName string, curl *charm.URL, forced bool, bundle charm.Charm, cons constraints.Value) *state.Service { 713 service, err := s.State.Service(serviceName) 714 c.Assert(err, gc.IsNil) 715 charm, force, err := service.Charm() 716 c.Assert(err, gc.IsNil) 717 c.Assert(force, gc.Equals, forced) 718 c.Assert(charm.URL(), gc.DeepEquals, curl) 719 c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta()) 720 c.Assert(charm.Config(), gc.DeepEquals, bundle.Config()) 721 722 serviceCons, err := service.Constraints() 723 c.Assert(err, gc.IsNil) 724 c.Assert(serviceCons, gc.DeepEquals, cons) 725 units, err := service.AllUnits() 726 c.Assert(err, gc.IsNil) 727 for _, unit := range units { 728 mid, err := unit.AssignedMachineId() 729 c.Assert(err, gc.IsNil) 730 machine, err := s.State.Machine(mid) 731 c.Assert(err, gc.IsNil) 732 machineCons, err := machine.Constraints() 733 c.Assert(err, gc.IsNil) 734 c.Assert(machineCons, gc.DeepEquals, cons) 735 } 736 return service 737 } 738 739 func (s *clientSuite) TestClientServiceDeployPrincipal(c *gc.C) { 740 // TODO(fwereade): test ToMachineSpec directly on srvClient, when we 741 // manage to extract it as a package and can thus do it conveniently. 742 store, restore := makeMockCharmStore() 743 defer restore() 744 curl, bundle := addCharm(c, store, "dummy") 745 mem4g := constraints.MustParse("mem=4G") 746 err := s.APIState.Client().ServiceDeploy( 747 curl.String(), "service", 3, "", mem4g, "", 748 ) 749 c.Assert(err, gc.IsNil) 750 s.assertPrincipalDeployed(c, "service", curl, false, bundle, mem4g) 751 } 752 753 func (s *clientSuite) TestClientServiceDeploySubordinate(c *gc.C) { 754 store, restore := makeMockCharmStore() 755 defer restore() 756 curl, bundle := addCharm(c, store, "logging") 757 err := s.APIState.Client().ServiceDeploy( 758 curl.String(), "service-name", 0, "", constraints.Value{}, "", 759 ) 760 service, err := s.State.Service("service-name") 761 c.Assert(err, gc.IsNil) 762 charm, force, err := service.Charm() 763 c.Assert(err, gc.IsNil) 764 c.Assert(force, gc.Equals, false) 765 c.Assert(charm.URL(), gc.DeepEquals, curl) 766 c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta()) 767 c.Assert(charm.Config(), gc.DeepEquals, bundle.Config()) 768 769 units, err := service.AllUnits() 770 c.Assert(err, gc.IsNil) 771 c.Assert(units, gc.HasLen, 0) 772 } 773 774 func (s *clientSuite) TestClientServiceDeployConfig(c *gc.C) { 775 // TODO(fwereade): test Config/ConfigYAML handling directly on srvClient. 776 // Can't be done cleanly until it's extracted similarly to Machiner. 777 store, restore := makeMockCharmStore() 778 defer restore() 779 curl, _ := addCharm(c, store, "dummy") 780 err := s.APIState.Client().ServiceDeploy( 781 curl.String(), "service-name", 1, "service-name:\n username: fred", constraints.Value{}, "", 782 ) 783 c.Assert(err, gc.IsNil) 784 service, err := s.State.Service("service-name") 785 c.Assert(err, gc.IsNil) 786 settings, err := service.ConfigSettings() 787 c.Assert(err, gc.IsNil) 788 c.Assert(settings, gc.DeepEquals, charm.Settings{"username": "fred"}) 789 } 790 791 func (s *clientSuite) TestClientServiceDeployConfigError(c *gc.C) { 792 // TODO(fwereade): test Config/ConfigYAML handling directly on srvClient. 793 // Can't be done cleanly until it's extracted similarly to Machiner. 794 store, restore := makeMockCharmStore() 795 defer restore() 796 curl, _ := addCharm(c, store, "dummy") 797 err := s.APIState.Client().ServiceDeploy( 798 curl.String(), "service-name", 1, "service-name:\n skill-level: fred", constraints.Value{}, "", 799 ) 800 c.Assert(err, gc.ErrorMatches, `option "skill-level" expected int, got "fred"`) 801 _, err = s.State.Service("service-name") 802 c.Assert(err, jc.Satisfies, errors.IsNotFound) 803 } 804 805 func (s *clientSuite) TestClientServiceDeployToMachine(c *gc.C) { 806 store, restore := makeMockCharmStore() 807 defer restore() 808 curl, bundle := addCharm(c, store, "dummy") 809 810 machine, err := s.State.AddMachine("precise", state.JobHostUnits) 811 c.Assert(err, gc.IsNil) 812 err = s.APIState.Client().ServiceDeploy( 813 curl.String(), "service-name", 1, "service-name:\n username: fred", constraints.Value{}, machine.Id(), 814 ) 815 c.Assert(err, gc.IsNil) 816 817 service, err := s.State.Service("service-name") 818 c.Assert(err, gc.IsNil) 819 charm, force, err := service.Charm() 820 c.Assert(err, gc.IsNil) 821 c.Assert(force, gc.Equals, false) 822 c.Assert(charm.URL(), gc.DeepEquals, curl) 823 c.Assert(charm.Meta(), gc.DeepEquals, bundle.Meta()) 824 c.Assert(charm.Config(), gc.DeepEquals, bundle.Config()) 825 826 units, err := service.AllUnits() 827 c.Assert(err, gc.IsNil) 828 c.Assert(units, gc.HasLen, 1) 829 mid, err := units[0].AssignedMachineId() 830 c.Assert(err, gc.IsNil) 831 c.Assert(mid, gc.Equals, machine.Id()) 832 } 833 834 func (s *clientSuite) TestClientServiceDeployServiceOwner(c *gc.C) { 835 store, restore := makeMockCharmStore() 836 defer restore() 837 curl, _ := addCharm(c, store, "dummy") 838 839 s.AddUser(c, "foobar") 840 s.APIState = s.OpenAPIAs(c, "user-foobar", "password") 841 842 err := s.APIState.Client().ServiceDeploy( 843 curl.String(), "service", 3, "", constraints.Value{}, "", 844 ) 845 c.Assert(err, gc.IsNil) 846 847 service, err := s.State.Service("service") 848 c.Assert(err, gc.IsNil) 849 c.Assert(service.GetOwnerTag(), gc.Equals, "user-foobar") 850 } 851 852 func (s *clientSuite) deployServiceForTests(c *gc.C, store *charmtesting.MockCharmStore) { 853 curl, _ := addCharm(c, store, "dummy") 854 err := s.APIState.Client().ServiceDeploy(curl.String(), 855 "service", 1, "", constraints.Value{}, "", 856 ) 857 c.Assert(err, gc.IsNil) 858 } 859 860 func (s *clientSuite) checkClientServiceUpdateSetCharm(c *gc.C, forceCharmUrl bool) { 861 store, restore := makeMockCharmStore() 862 defer restore() 863 s.deployServiceForTests(c, store) 864 addCharm(c, store, "wordpress") 865 866 // Update the charm for the service. 867 args := params.ServiceUpdate{ 868 ServiceName: "service", 869 CharmUrl: "cs:precise/wordpress-3", 870 ForceCharmUrl: forceCharmUrl, 871 } 872 err := s.APIState.Client().ServiceUpdate(args) 873 c.Assert(err, gc.IsNil) 874 875 // Ensure the charm has been updated and and the force flag correctly set. 876 service, err := s.State.Service("service") 877 c.Assert(err, gc.IsNil) 878 ch, force, err := service.Charm() 879 c.Assert(err, gc.IsNil) 880 c.Assert(ch.URL().String(), gc.Equals, "cs:precise/wordpress-3") 881 c.Assert(force, gc.Equals, forceCharmUrl) 882 } 883 884 func (s *clientSuite) TestClientServiceUpdateSetCharm(c *gc.C) { 885 s.checkClientServiceUpdateSetCharm(c, false) 886 } 887 888 func (s *clientSuite) TestClientServiceUpdateForceSetCharm(c *gc.C) { 889 s.checkClientServiceUpdateSetCharm(c, true) 890 } 891 892 func (s *clientSuite) TestClientServiceUpdateSetCharmErrors(c *gc.C) { 893 _, restore := makeMockCharmStore() 894 defer restore() 895 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 896 for charmUrl, expect := range map[string]string{ 897 "wordpress": "charm url series is not resolved", 898 "cs:wordpress": "charm url series is not resolved", 899 "cs:precise/wordpress": "charm url must include revision", 900 "cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`, 901 } { 902 c.Logf("test %s", charmUrl) 903 args := params.ServiceUpdate{ 904 ServiceName: "wordpress", 905 CharmUrl: charmUrl, 906 } 907 err := s.APIState.Client().ServiceUpdate(args) 908 c.Check(err, gc.ErrorMatches, expect) 909 } 910 } 911 912 func (s *clientSuite) TestClientServiceUpdateSetMinUnits(c *gc.C) { 913 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 914 915 // Set minimum units for the service. 916 minUnits := 2 917 args := params.ServiceUpdate{ 918 ServiceName: "dummy", 919 MinUnits: &minUnits, 920 } 921 err := s.APIState.Client().ServiceUpdate(args) 922 c.Assert(err, gc.IsNil) 923 924 // Ensure the minimum number of units has been set. 925 c.Assert(service.Refresh(), gc.IsNil) 926 c.Assert(service.MinUnits(), gc.Equals, minUnits) 927 } 928 929 func (s *clientSuite) TestClientServiceUpdateSetMinUnitsError(c *gc.C) { 930 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 931 932 // Set a negative minimum number of units for the service. 933 minUnits := -1 934 args := params.ServiceUpdate{ 935 ServiceName: "dummy", 936 MinUnits: &minUnits, 937 } 938 err := s.APIState.Client().ServiceUpdate(args) 939 c.Assert(err, gc.ErrorMatches, 940 `cannot set minimum units for service "dummy": cannot set a negative minimum number of units`) 941 942 // Ensure the minimum number of units has not been set. 943 c.Assert(service.Refresh(), gc.IsNil) 944 c.Assert(service.MinUnits(), gc.Equals, 0) 945 } 946 947 func (s *clientSuite) TestClientServiceUpdateSetSettingsStrings(c *gc.C) { 948 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 949 950 // Update settings for the service. 951 args := params.ServiceUpdate{ 952 ServiceName: "dummy", 953 SettingsStrings: map[string]string{"title": "s-title", "username": "s-user"}, 954 } 955 err := s.APIState.Client().ServiceUpdate(args) 956 c.Assert(err, gc.IsNil) 957 958 // Ensure the settings have been correctly updated. 959 expected := charm.Settings{"title": "s-title", "username": "s-user"} 960 obtained, err := service.ConfigSettings() 961 c.Assert(err, gc.IsNil) 962 c.Assert(obtained, gc.DeepEquals, expected) 963 } 964 965 func (s *clientSuite) TestClientServiceUpdateSetSettingsYAML(c *gc.C) { 966 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 967 968 // Update settings for the service. 969 args := params.ServiceUpdate{ 970 ServiceName: "dummy", 971 SettingsYAML: "dummy:\n title: y-title\n username: y-user", 972 } 973 err := s.APIState.Client().ServiceUpdate(args) 974 c.Assert(err, gc.IsNil) 975 976 // Ensure the settings have been correctly updated. 977 expected := charm.Settings{"title": "y-title", "username": "y-user"} 978 obtained, err := service.ConfigSettings() 979 c.Assert(err, gc.IsNil) 980 c.Assert(obtained, gc.DeepEquals, expected) 981 } 982 983 func (s *clientSuite) TestClientServiceUpdateSetConstraints(c *gc.C) { 984 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 985 986 // Update constraints for the service. 987 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 988 c.Assert(err, gc.IsNil) 989 args := params.ServiceUpdate{ 990 ServiceName: "dummy", 991 Constraints: &cons, 992 } 993 err = s.APIState.Client().ServiceUpdate(args) 994 c.Assert(err, gc.IsNil) 995 996 // Ensure the constraints have been correctly updated. 997 obtained, err := service.Constraints() 998 c.Assert(err, gc.IsNil) 999 c.Assert(obtained, gc.DeepEquals, cons) 1000 } 1001 1002 func (s *clientSuite) TestClientServiceUpdateAllParams(c *gc.C) { 1003 store, restore := makeMockCharmStore() 1004 defer restore() 1005 s.deployServiceForTests(c, store) 1006 addCharm(c, store, "wordpress") 1007 1008 // Update all the service attributes. 1009 minUnits := 3 1010 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 1011 c.Assert(err, gc.IsNil) 1012 args := params.ServiceUpdate{ 1013 ServiceName: "service", 1014 CharmUrl: "cs:precise/wordpress-3", 1015 ForceCharmUrl: true, 1016 MinUnits: &minUnits, 1017 SettingsStrings: map[string]string{"blog-title": "string-title"}, 1018 SettingsYAML: "service:\n blog-title: yaml-title\n", 1019 Constraints: &cons, 1020 } 1021 err = s.APIState.Client().ServiceUpdate(args) 1022 c.Assert(err, gc.IsNil) 1023 1024 // Ensure the service has been correctly updated. 1025 service, err := s.State.Service("service") 1026 c.Assert(err, gc.IsNil) 1027 1028 // Check the charm. 1029 ch, force, err := service.Charm() 1030 c.Assert(err, gc.IsNil) 1031 c.Assert(ch.URL().String(), gc.Equals, "cs:precise/wordpress-3") 1032 c.Assert(force, gc.Equals, true) 1033 1034 // Check the minimum number of units. 1035 c.Assert(service.MinUnits(), gc.Equals, minUnits) 1036 1037 // Check the settings: also ensure the YAML settings take precedence 1038 // over strings ones. 1039 expectedSettings := charm.Settings{"blog-title": "yaml-title"} 1040 obtainedSettings, err := service.ConfigSettings() 1041 c.Assert(err, gc.IsNil) 1042 c.Assert(obtainedSettings, gc.DeepEquals, expectedSettings) 1043 1044 // Check the constraints. 1045 obtainedConstraints, err := service.Constraints() 1046 c.Assert(err, gc.IsNil) 1047 c.Assert(obtainedConstraints, gc.DeepEquals, cons) 1048 } 1049 1050 func (s *clientSuite) TestClientServiceUpdateNoParams(c *gc.C) { 1051 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1052 1053 // Calling ServiceUpdate with no parameters set is a no-op. 1054 args := params.ServiceUpdate{ServiceName: "wordpress"} 1055 err := s.APIState.Client().ServiceUpdate(args) 1056 c.Assert(err, gc.IsNil) 1057 } 1058 1059 func (s *clientSuite) TestClientServiceUpdateNoService(c *gc.C) { 1060 err := s.APIState.Client().ServiceUpdate(params.ServiceUpdate{}) 1061 c.Assert(err, gc.ErrorMatches, `"" is not a valid service name`) 1062 } 1063 1064 func (s *clientSuite) TestClientServiceUpdateInvalidService(c *gc.C) { 1065 args := params.ServiceUpdate{ServiceName: "no-such-service"} 1066 err := s.APIState.Client().ServiceUpdate(args) 1067 c.Assert(err, gc.ErrorMatches, `service "no-such-service" not found`) 1068 } 1069 1070 func (s *clientSuite) TestClientServiceSetCharm(c *gc.C) { 1071 store, restore := makeMockCharmStore() 1072 defer restore() 1073 curl, _ := addCharm(c, store, "dummy") 1074 err := s.APIState.Client().ServiceDeploy( 1075 curl.String(), "service", 3, "", constraints.Value{}, "", 1076 ) 1077 c.Assert(err, gc.IsNil) 1078 addCharm(c, store, "wordpress") 1079 err = s.APIState.Client().ServiceSetCharm( 1080 "service", "cs:precise/wordpress-3", false, 1081 ) 1082 c.Assert(err, gc.IsNil) 1083 1084 // Ensure that the charm is not marked as forced. 1085 service, err := s.State.Service("service") 1086 c.Assert(err, gc.IsNil) 1087 charm, force, err := service.Charm() 1088 c.Assert(err, gc.IsNil) 1089 c.Assert(charm.URL().String(), gc.Equals, "cs:precise/wordpress-3") 1090 c.Assert(force, gc.Equals, false) 1091 } 1092 1093 func (s *clientSuite) TestClientServiceSetCharmForce(c *gc.C) { 1094 store, restore := makeMockCharmStore() 1095 defer restore() 1096 curl, _ := addCharm(c, store, "dummy") 1097 err := s.APIState.Client().ServiceDeploy( 1098 curl.String(), "service", 3, "", constraints.Value{}, "", 1099 ) 1100 c.Assert(err, gc.IsNil) 1101 addCharm(c, store, "wordpress") 1102 err = s.APIState.Client().ServiceSetCharm( 1103 "service", "cs:precise/wordpress-3", true, 1104 ) 1105 c.Assert(err, gc.IsNil) 1106 1107 // Ensure that the charm is marked as forced. 1108 service, err := s.State.Service("service") 1109 c.Assert(err, gc.IsNil) 1110 charm, force, err := service.Charm() 1111 c.Assert(err, gc.IsNil) 1112 c.Assert(charm.URL().String(), gc.Equals, "cs:precise/wordpress-3") 1113 c.Assert(force, gc.Equals, true) 1114 } 1115 1116 func (s *clientSuite) TestClientServiceSetCharmInvalidService(c *gc.C) { 1117 _, restore := makeMockCharmStore() 1118 defer restore() 1119 err := s.APIState.Client().ServiceSetCharm( 1120 "badservice", "cs:precise/wordpress-3", true, 1121 ) 1122 c.Assert(err, gc.ErrorMatches, `service "badservice" not found`) 1123 } 1124 1125 func (s *clientSuite) TestClientServiceSetCharmErrors(c *gc.C) { 1126 _, restore := makeMockCharmStore() 1127 defer restore() 1128 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1129 for url, expect := range map[string]string{ 1130 // TODO(fwereade,Makyo) make these errors consistent one day. 1131 "wordpress": "charm url series is not resolved", 1132 "cs:wordpress": "charm url series is not resolved", 1133 "cs:precise/wordpress": "charm url must include revision", 1134 "cs:precise/wordpress-999999": `cannot download charm ".*": charm not found in mock store: cs:precise/wordpress-999999`, 1135 } { 1136 c.Logf("test %s", url) 1137 err := s.APIState.Client().ServiceSetCharm( 1138 "wordpress", url, false, 1139 ) 1140 c.Check(err, gc.ErrorMatches, expect) 1141 } 1142 } 1143 1144 func makeMockCharmStore() (store *charmtesting.MockCharmStore, restore func()) { 1145 mockStore := charmtesting.NewMockCharmStore() 1146 origStore := client.CharmStore 1147 client.CharmStore = mockStore 1148 return mockStore, func() { client.CharmStore = origStore } 1149 } 1150 1151 func addCharm(c *gc.C, store *charmtesting.MockCharmStore, name string) (*charm.URL, charm.Charm) { 1152 return addSeriesCharm(c, store, "precise", name) 1153 } 1154 1155 func addSeriesCharm(c *gc.C, store *charmtesting.MockCharmStore, series, name string) (*charm.URL, charm.Charm) { 1156 bundle := charmtesting.Charms.Bundle(c.MkDir(), name) 1157 scurl := fmt.Sprintf("cs:%s/%s-%d", series, name, bundle.Revision()) 1158 curl := charm.MustParseURL(scurl) 1159 err := store.SetCharm(curl, bundle) 1160 c.Assert(err, gc.IsNil) 1161 return curl, bundle 1162 } 1163 1164 func (s *clientSuite) checkEndpoints(c *gc.C, endpoints map[string]charm.Relation) { 1165 c.Assert(endpoints["wordpress"], gc.DeepEquals, charm.Relation{ 1166 Name: "db", 1167 Role: charm.RelationRole("requirer"), 1168 Interface: "mysql", 1169 Optional: false, 1170 Limit: 1, 1171 Scope: charm.RelationScope("global"), 1172 }) 1173 c.Assert(endpoints["mysql"], gc.DeepEquals, charm.Relation{ 1174 Name: "server", 1175 Role: charm.RelationRole("provider"), 1176 Interface: "mysql", 1177 Optional: false, 1178 Limit: 0, 1179 Scope: charm.RelationScope("global"), 1180 }) 1181 } 1182 1183 func (s *clientSuite) assertAddRelation(c *gc.C, endpoints []string) { 1184 s.setUpScenario(c) 1185 res, err := s.APIState.Client().AddRelation(endpoints...) 1186 c.Assert(err, gc.IsNil) 1187 s.checkEndpoints(c, res.Endpoints) 1188 // Show that the relation was added. 1189 wpSvc, err := s.State.Service("wordpress") 1190 c.Assert(err, gc.IsNil) 1191 rels, err := wpSvc.Relations() 1192 // There are 2 relations - the logging-wordpress one set up in the 1193 // scenario and the one created in this test. 1194 c.Assert(len(rels), gc.Equals, 2) 1195 mySvc, err := s.State.Service("mysql") 1196 c.Assert(err, gc.IsNil) 1197 rels, err = mySvc.Relations() 1198 c.Assert(len(rels), gc.Equals, 1) 1199 } 1200 1201 func (s *clientSuite) TestSuccessfullyAddRelation(c *gc.C) { 1202 endpoints := []string{"wordpress", "mysql"} 1203 s.assertAddRelation(c, endpoints) 1204 } 1205 1206 func (s *clientSuite) TestSuccessfullyAddRelationSwapped(c *gc.C) { 1207 // Show that the order of the services listed in the AddRelation call 1208 // does not matter. This is a repeat of the previous test with the service 1209 // names swapped. 1210 endpoints := []string{"mysql", "wordpress"} 1211 s.assertAddRelation(c, endpoints) 1212 } 1213 1214 func (s *clientSuite) TestCallWithOnlyOneEndpoint(c *gc.C) { 1215 s.setUpScenario(c) 1216 endpoints := []string{"wordpress"} 1217 _, err := s.APIState.Client().AddRelation(endpoints...) 1218 c.Assert(err, gc.ErrorMatches, "no relations found") 1219 } 1220 1221 func (s *clientSuite) TestCallWithOneEndpointTooMany(c *gc.C) { 1222 s.setUpScenario(c) 1223 endpoints := []string{"wordpress", "mysql", "logging"} 1224 _, err := s.APIState.Client().AddRelation(endpoints...) 1225 c.Assert(err, gc.ErrorMatches, "cannot relate 3 endpoints") 1226 } 1227 1228 func (s *clientSuite) TestAddAlreadyAddedRelation(c *gc.C) { 1229 s.setUpScenario(c) 1230 // Add a relation between wordpress and mysql. 1231 endpoints := []string{"wordpress", "mysql"} 1232 eps, err := s.State.InferEndpoints(endpoints) 1233 c.Assert(err, gc.IsNil) 1234 _, err = s.State.AddRelation(eps...) 1235 c.Assert(err, gc.IsNil) 1236 // And try to add it again. 1237 _, err = s.APIState.Client().AddRelation(endpoints...) 1238 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation already exists`) 1239 } 1240 1241 func (s *clientSuite) assertDestroyRelation(c *gc.C, endpoints []string) { 1242 s.setUpScenario(c) 1243 // Add a relation between the endpoints. 1244 eps, err := s.State.InferEndpoints(endpoints) 1245 c.Assert(err, gc.IsNil) 1246 relation, err := s.State.AddRelation(eps...) 1247 c.Assert(err, gc.IsNil) 1248 1249 err = s.APIState.Client().DestroyRelation(endpoints...) 1250 c.Assert(err, gc.IsNil) 1251 // Show that the relation was removed. 1252 c.Assert(relation.Refresh(), jc.Satisfies, errors.IsNotFound) 1253 } 1254 1255 func (s *clientSuite) TestSuccessfulDestroyRelation(c *gc.C) { 1256 endpoints := []string{"wordpress", "mysql"} 1257 s.assertDestroyRelation(c, endpoints) 1258 } 1259 1260 func (s *clientSuite) TestSuccessfullyDestroyRelationSwapped(c *gc.C) { 1261 // Show that the order of the services listed in the DestroyRelation call 1262 // does not matter. This is a repeat of the previous test with the service 1263 // names swapped. 1264 endpoints := []string{"mysql", "wordpress"} 1265 s.assertDestroyRelation(c, endpoints) 1266 } 1267 1268 func (s *clientSuite) TestNoRelation(c *gc.C) { 1269 s.setUpScenario(c) 1270 endpoints := []string{"wordpress", "mysql"} 1271 err := s.APIState.Client().DestroyRelation(endpoints...) 1272 c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`) 1273 } 1274 1275 func (s *clientSuite) TestAttemptDestroyingNonExistentRelation(c *gc.C) { 1276 s.setUpScenario(c) 1277 s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak")) 1278 endpoints := []string{"riak", "wordpress"} 1279 err := s.APIState.Client().DestroyRelation(endpoints...) 1280 c.Assert(err, gc.ErrorMatches, "no relations found") 1281 } 1282 1283 func (s *clientSuite) TestAttemptDestroyingWithOnlyOneEndpoint(c *gc.C) { 1284 s.setUpScenario(c) 1285 endpoints := []string{"wordpress"} 1286 err := s.APIState.Client().DestroyRelation(endpoints...) 1287 c.Assert(err, gc.ErrorMatches, "no relations found") 1288 } 1289 1290 func (s *clientSuite) TestAttemptDestroyingPeerRelation(c *gc.C) { 1291 s.setUpScenario(c) 1292 s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak")) 1293 1294 endpoints := []string{"riak:ring"} 1295 err := s.APIState.Client().DestroyRelation(endpoints...) 1296 c.Assert(err, gc.ErrorMatches, `cannot destroy relation "riak:ring": is a peer relation`) 1297 } 1298 1299 func (s *clientSuite) TestAttemptDestroyingAlreadyDestroyedRelation(c *gc.C) { 1300 s.setUpScenario(c) 1301 1302 // Add a relation between wordpress and mysql. 1303 eps, err := s.State.InferEndpoints([]string{"wordpress", "mysql"}) 1304 c.Assert(err, gc.IsNil) 1305 rel, err := s.State.AddRelation(eps...) 1306 c.Assert(err, gc.IsNil) 1307 1308 endpoints := []string{"wordpress", "mysql"} 1309 err = s.APIState.Client().DestroyRelation(endpoints...) 1310 // Show that the relation was removed. 1311 c.Assert(rel.Refresh(), jc.Satisfies, errors.IsNotFound) 1312 1313 // And try to destroy it again. 1314 err = s.APIState.Client().DestroyRelation(endpoints...) 1315 c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`) 1316 } 1317 1318 func (s *clientSuite) TestClientWatchAll(c *gc.C) { 1319 // A very simple end-to-end test, because 1320 // all the logic is tested elsewhere. 1321 m, err := s.State.AddMachine("quantal", state.JobManageEnviron) 1322 c.Assert(err, gc.IsNil) 1323 err = m.SetProvisioned("i-0", state.BootstrapNonce, nil) 1324 c.Assert(err, gc.IsNil) 1325 watcher, err := s.APIState.Client().WatchAll() 1326 c.Assert(err, gc.IsNil) 1327 defer func() { 1328 err := watcher.Stop() 1329 c.Assert(err, gc.IsNil) 1330 }() 1331 deltas, err := watcher.Next() 1332 c.Assert(err, gc.IsNil) 1333 if !c.Check(deltas, gc.DeepEquals, []params.Delta{{ 1334 Entity: ¶ms.MachineInfo{ 1335 Id: m.Id(), 1336 InstanceId: "i-0", 1337 Status: params.StatusPending, 1338 Life: params.Alive, 1339 Series: "quantal", 1340 Jobs: []params.MachineJob{state.JobManageEnviron.ToParams()}, 1341 Addresses: []instance.Address{}, 1342 HardwareCharacteristics: &instance.HardwareCharacteristics{}, 1343 }, 1344 }}) { 1345 c.Logf("got:") 1346 for _, d := range deltas { 1347 c.Logf("%#v\n", d.Entity) 1348 } 1349 } 1350 } 1351 1352 func (s *clientSuite) TestClientSetServiceConstraints(c *gc.C) { 1353 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1354 1355 // Update constraints for the service. 1356 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 1357 c.Assert(err, gc.IsNil) 1358 err = s.APIState.Client().SetServiceConstraints("dummy", cons) 1359 c.Assert(err, gc.IsNil) 1360 1361 // Ensure the constraints have been correctly updated. 1362 obtained, err := service.Constraints() 1363 c.Assert(err, gc.IsNil) 1364 c.Assert(obtained, gc.DeepEquals, cons) 1365 } 1366 1367 func (s *clientSuite) TestClientGetServiceConstraints(c *gc.C) { 1368 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1369 1370 // Set constraints for the service. 1371 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 1372 c.Assert(err, gc.IsNil) 1373 err = service.SetConstraints(cons) 1374 c.Assert(err, gc.IsNil) 1375 1376 // Check we can get the constraints. 1377 obtained, err := s.APIState.Client().GetServiceConstraints("dummy") 1378 c.Assert(err, gc.IsNil) 1379 c.Assert(obtained, gc.DeepEquals, cons) 1380 } 1381 1382 func (s *clientSuite) TestClientSetEnvironmentConstraints(c *gc.C) { 1383 // Set constraints for the environment. 1384 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 1385 c.Assert(err, gc.IsNil) 1386 err = s.APIState.Client().SetEnvironmentConstraints(cons) 1387 c.Assert(err, gc.IsNil) 1388 1389 // Ensure the constraints have been correctly updated. 1390 obtained, err := s.State.EnvironConstraints() 1391 c.Assert(err, gc.IsNil) 1392 c.Assert(obtained, gc.DeepEquals, cons) 1393 } 1394 1395 func (s *clientSuite) TestClientGetEnvironmentConstraints(c *gc.C) { 1396 // Set constraints for the environment. 1397 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 1398 c.Assert(err, gc.IsNil) 1399 err = s.State.SetEnvironConstraints(cons) 1400 c.Assert(err, gc.IsNil) 1401 1402 // Check we can get the constraints. 1403 obtained, err := s.APIState.Client().GetEnvironmentConstraints() 1404 c.Assert(err, gc.IsNil) 1405 c.Assert(obtained, gc.DeepEquals, cons) 1406 } 1407 1408 func (s *clientSuite) TestClientServiceCharmRelations(c *gc.C) { 1409 s.setUpScenario(c) 1410 _, err := s.APIState.Client().ServiceCharmRelations("blah") 1411 c.Assert(err, gc.ErrorMatches, `service "blah" not found`) 1412 1413 relations, err := s.APIState.Client().ServiceCharmRelations("wordpress") 1414 c.Assert(err, gc.IsNil) 1415 c.Assert(relations, gc.DeepEquals, []string{ 1416 "cache", "db", "juju-info", "logging-dir", "monitoring-port", "url", 1417 }) 1418 } 1419 1420 func (s *clientSuite) TestClientPublicAddressErrors(c *gc.C) { 1421 s.setUpScenario(c) 1422 _, err := s.APIState.Client().PublicAddress("wordpress") 1423 c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`) 1424 _, err = s.APIState.Client().PublicAddress("0") 1425 c.Assert(err, gc.ErrorMatches, `machine "0" has no public address`) 1426 _, err = s.APIState.Client().PublicAddress("wordpress/0") 1427 c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" has no public address`) 1428 } 1429 1430 func (s *clientSuite) TestClientPublicAddressMachine(c *gc.C) { 1431 s.setUpScenario(c) 1432 1433 // Internally, instance.SelectPublicAddress is used; the "most public" 1434 // address is returned. 1435 m1, err := s.State.Machine("1") 1436 c.Assert(err, gc.IsNil) 1437 cloudLocalAddress := instance.NewAddress("cloudlocal", instance.NetworkCloudLocal) 1438 publicAddress := instance.NewAddress("public", instance.NetworkPublic) 1439 err = m1.SetAddresses(cloudLocalAddress) 1440 c.Assert(err, gc.IsNil) 1441 addr, err := s.APIState.Client().PublicAddress("1") 1442 c.Assert(err, gc.IsNil) 1443 c.Assert(addr, gc.Equals, "cloudlocal") 1444 err = m1.SetAddresses(cloudLocalAddress, publicAddress) 1445 addr, err = s.APIState.Client().PublicAddress("1") 1446 c.Assert(err, gc.IsNil) 1447 c.Assert(addr, gc.Equals, "public") 1448 } 1449 1450 func (s *clientSuite) TestClientPublicAddressUnit(c *gc.C) { 1451 s.setUpScenario(c) 1452 1453 m1, err := s.State.Machine("1") 1454 publicAddress := instance.NewAddress("public", instance.NetworkPublic) 1455 err = m1.SetAddresses(publicAddress) 1456 c.Assert(err, gc.IsNil) 1457 addr, err := s.APIState.Client().PublicAddress("wordpress/0") 1458 c.Assert(err, gc.IsNil) 1459 c.Assert(addr, gc.Equals, "public") 1460 } 1461 1462 func (s *clientSuite) TestClientPrivateAddressErrors(c *gc.C) { 1463 s.setUpScenario(c) 1464 _, err := s.APIState.Client().PrivateAddress("wordpress") 1465 c.Assert(err, gc.ErrorMatches, `unknown unit or machine "wordpress"`) 1466 _, err = s.APIState.Client().PrivateAddress("0") 1467 c.Assert(err, gc.ErrorMatches, `machine "0" has no internal address`) 1468 _, err = s.APIState.Client().PrivateAddress("wordpress/0") 1469 c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" has no internal address`) 1470 } 1471 1472 func (s *clientSuite) TestClientPrivateAddress(c *gc.C) { 1473 s.setUpScenario(c) 1474 1475 // Internally, instance.SelectInternalAddress is used; the public 1476 // address if no cloud-local one is available. 1477 m1, err := s.State.Machine("1") 1478 c.Assert(err, gc.IsNil) 1479 cloudLocalAddress := instance.NewAddress("cloudlocal", instance.NetworkCloudLocal) 1480 publicAddress := instance.NewAddress("public", instance.NetworkPublic) 1481 err = m1.SetAddresses(publicAddress) 1482 c.Assert(err, gc.IsNil) 1483 addr, err := s.APIState.Client().PrivateAddress("1") 1484 c.Assert(err, gc.IsNil) 1485 c.Assert(addr, gc.Equals, "public") 1486 err = m1.SetAddresses(cloudLocalAddress, publicAddress) 1487 addr, err = s.APIState.Client().PrivateAddress("1") 1488 c.Assert(err, gc.IsNil) 1489 c.Assert(addr, gc.Equals, "cloudlocal") 1490 } 1491 1492 func (s *clientSuite) TestClientPrivateAddressUnit(c *gc.C) { 1493 s.setUpScenario(c) 1494 1495 m1, err := s.State.Machine("1") 1496 privateAddress := instance.NewAddress("private", instance.NetworkCloudLocal) 1497 err = m1.SetAddresses(privateAddress) 1498 c.Assert(err, gc.IsNil) 1499 addr, err := s.APIState.Client().PrivateAddress("wordpress/0") 1500 c.Assert(err, gc.IsNil) 1501 c.Assert(addr, gc.Equals, "private") 1502 } 1503 1504 func (s *clientSuite) TestClientEnvironmentGet(c *gc.C) { 1505 envConfig, err := s.State.EnvironConfig() 1506 c.Assert(err, gc.IsNil) 1507 attrs, err := s.APIState.Client().EnvironmentGet() 1508 c.Assert(err, gc.IsNil) 1509 allAttrs := envConfig.AllAttrs() 1510 // We cannot simply use DeepEquals, because after the 1511 // map[string]interface{} result of EnvironmentGet is 1512 // serialized to JSON, integers are converted to floats. 1513 for key, apiValue := range attrs { 1514 envValue, found := allAttrs[key] 1515 c.Check(found, jc.IsTrue) 1516 switch apiValue.(type) { 1517 case float64, float32: 1518 c.Check(fmt.Sprintf("%v", envValue), gc.Equals, fmt.Sprintf("%v", apiValue)) 1519 default: 1520 c.Check(envValue, gc.Equals, apiValue) 1521 } 1522 } 1523 } 1524 1525 func (s *clientSuite) TestClientEnvironmentSet(c *gc.C) { 1526 envConfig, err := s.State.EnvironConfig() 1527 c.Assert(err, gc.IsNil) 1528 _, found := envConfig.AllAttrs()["some-key"] 1529 c.Assert(found, jc.IsFalse) 1530 1531 args := map[string]interface{}{"some-key": "value"} 1532 err = s.APIState.Client().EnvironmentSet(args) 1533 c.Assert(err, gc.IsNil) 1534 1535 envConfig, err = s.State.EnvironConfig() 1536 c.Assert(err, gc.IsNil) 1537 value, found := envConfig.AllAttrs()["some-key"] 1538 c.Assert(found, jc.IsTrue) 1539 c.Assert(value, gc.Equals, "value") 1540 } 1541 1542 func (s *clientSuite) TestClientSetEnvironAgentVersion(c *gc.C) { 1543 err := s.APIState.Client().SetEnvironAgentVersion(version.MustParse("9.8.7")) 1544 c.Assert(err, gc.IsNil) 1545 1546 envConfig, err := s.State.EnvironConfig() 1547 c.Assert(err, gc.IsNil) 1548 agentVersion, found := envConfig.AllAttrs()["agent-version"] 1549 c.Assert(found, jc.IsTrue) 1550 c.Assert(agentVersion, gc.Equals, "9.8.7") 1551 } 1552 1553 func (s *clientSuite) TestClientEnvironmentSetCannotChangeAgentVersion(c *gc.C) { 1554 args := map[string]interface{}{"agent-version": "9.9.9"} 1555 err := s.APIState.Client().EnvironmentSet(args) 1556 c.Assert(err, gc.ErrorMatches, "agent-version cannot be changed") 1557 // It's okay to pass env back with the same agent-version. 1558 cfg, err := s.APIState.Client().EnvironmentGet() 1559 c.Assert(err, gc.IsNil) 1560 c.Assert(cfg["agent-version"], gc.NotNil) 1561 err = s.APIState.Client().EnvironmentSet(cfg) 1562 c.Assert(err, gc.IsNil) 1563 } 1564 1565 func (s *clientSuite) TestClientEnvironmentUnset(c *gc.C) { 1566 err := s.State.UpdateEnvironConfig(map[string]interface{}{"abc": 123}, nil, nil) 1567 c.Assert(err, gc.IsNil) 1568 envConfig, err := s.State.EnvironConfig() 1569 c.Assert(err, gc.IsNil) 1570 _, found := envConfig.AllAttrs()["abc"] 1571 c.Assert(found, jc.IsTrue) 1572 1573 err = s.APIState.Client().EnvironmentUnset("abc") 1574 c.Assert(err, gc.IsNil) 1575 envConfig, err = s.State.EnvironConfig() 1576 c.Assert(err, gc.IsNil) 1577 _, found = envConfig.AllAttrs()["abc"] 1578 c.Assert(found, jc.IsFalse) 1579 } 1580 1581 func (s *clientSuite) TestClientEnvironmentUnsetMissing(c *gc.C) { 1582 // It's okay to unset a non-existent attribute. 1583 err := s.APIState.Client().EnvironmentUnset("not_there") 1584 c.Assert(err, gc.IsNil) 1585 } 1586 1587 func (s *clientSuite) TestClientEnvironmentUnsetError(c *gc.C) { 1588 err := s.State.UpdateEnvironConfig(map[string]interface{}{"abc": 123}, nil, nil) 1589 c.Assert(err, gc.IsNil) 1590 envConfig, err := s.State.EnvironConfig() 1591 c.Assert(err, gc.IsNil) 1592 _, found := envConfig.AllAttrs()["abc"] 1593 c.Assert(found, jc.IsTrue) 1594 1595 // "type" may not be removed, and this will cause an error. 1596 // If any one attribute's removal causes an error, there 1597 // should be no change. 1598 err = s.APIState.Client().EnvironmentUnset("abc", "type") 1599 c.Assert(err, gc.ErrorMatches, "type: expected string, got nothing") 1600 envConfig, err = s.State.EnvironConfig() 1601 c.Assert(err, gc.IsNil) 1602 _, found = envConfig.AllAttrs()["abc"] 1603 c.Assert(found, jc.IsTrue) 1604 } 1605 1606 func (s *clientSuite) TestClientFindTools(c *gc.C) { 1607 result, err := s.APIState.Client().FindTools(2, -1, "", "") 1608 c.Assert(err, gc.IsNil) 1609 c.Assert(result.Error, jc.Satisfies, params.IsCodeNotFound) 1610 toolstesting.UploadToStorage(c, s.Conn.Environ.Storage(), version.MustParseBinary("2.12.0-precise-amd64")) 1611 result, err = s.APIState.Client().FindTools(2, 12, "precise", "amd64") 1612 c.Assert(err, gc.IsNil) 1613 c.Assert(result.Error, gc.IsNil) 1614 c.Assert(result.List, gc.HasLen, 1) 1615 c.Assert(result.List[0].Version, gc.Equals, version.MustParseBinary("2.12.0-precise-amd64")) 1616 } 1617 1618 func (s *clientSuite) checkMachine(c *gc.C, id, series, cons string) { 1619 // Ensure the machine was actually created. 1620 machine, err := s.BackingState.Machine(id) 1621 c.Assert(err, gc.IsNil) 1622 c.Assert(machine.Series(), gc.Equals, series) 1623 c.Assert(machine.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobHostUnits}) 1624 machineConstraints, err := machine.Constraints() 1625 c.Assert(err, gc.IsNil) 1626 c.Assert(machineConstraints.String(), gc.Equals, cons) 1627 } 1628 1629 func (s *clientSuite) TestClientAddMachinesDefaultSeries(c *gc.C) { 1630 apiParams := make([]params.AddMachineParams, 3) 1631 for i := 0; i < 3; i++ { 1632 apiParams[i] = params.AddMachineParams{ 1633 Jobs: []params.MachineJob{params.JobHostUnits}, 1634 } 1635 } 1636 machines, err := s.APIState.Client().AddMachines(apiParams) 1637 c.Assert(err, gc.IsNil) 1638 c.Assert(len(machines), gc.Equals, 3) 1639 for i, machineResult := range machines { 1640 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 1641 s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String()) 1642 } 1643 } 1644 1645 func (s *clientSuite) TestClientAddMachinesWithSeries(c *gc.C) { 1646 apiParams := make([]params.AddMachineParams, 3) 1647 for i := 0; i < 3; i++ { 1648 apiParams[i] = params.AddMachineParams{ 1649 Series: "quantal", 1650 Jobs: []params.MachineJob{params.JobHostUnits}, 1651 } 1652 } 1653 machines, err := s.APIState.Client().AddMachines(apiParams) 1654 c.Assert(err, gc.IsNil) 1655 c.Assert(len(machines), gc.Equals, 3) 1656 for i, machineResult := range machines { 1657 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 1658 s.checkMachine(c, machineResult.Machine, "quantal", apiParams[i].Constraints.String()) 1659 } 1660 } 1661 1662 func (s *clientSuite) TestClientAddMachineInsideMachine(c *gc.C) { 1663 _, err := s.State.AddMachine("quantal", state.JobHostUnits) 1664 c.Assert(err, gc.IsNil) 1665 1666 machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{{ 1667 Jobs: []params.MachineJob{params.JobHostUnits}, 1668 ContainerType: instance.LXC, 1669 ParentId: "0", 1670 Series: "quantal", 1671 }}) 1672 c.Assert(err, gc.IsNil) 1673 c.Assert(machines, gc.HasLen, 1) 1674 c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0") 1675 } 1676 1677 func (s *clientSuite) TestClientAddMachinesWithConstraints(c *gc.C) { 1678 apiParams := make([]params.AddMachineParams, 3) 1679 for i := 0; i < 3; i++ { 1680 apiParams[i] = params.AddMachineParams{ 1681 Jobs: []params.MachineJob{params.JobHostUnits}, 1682 } 1683 } 1684 // The last machine has some constraints. 1685 apiParams[2].Constraints = constraints.MustParse("mem=4G") 1686 machines, err := s.APIState.Client().AddMachines(apiParams) 1687 c.Assert(err, gc.IsNil) 1688 c.Assert(len(machines), gc.Equals, 3) 1689 for i, machineResult := range machines { 1690 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 1691 s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String()) 1692 } 1693 } 1694 1695 func (s *clientSuite) TestClientAddMachinesWithPlacement(c *gc.C) { 1696 apiParams := make([]params.AddMachineParams, 4) 1697 for i := range apiParams { 1698 apiParams[i] = params.AddMachineParams{ 1699 Jobs: []params.MachineJob{params.JobHostUnits}, 1700 } 1701 } 1702 apiParams[0].Placement = instance.MustParsePlacement("lxc") 1703 apiParams[1].Placement = instance.MustParsePlacement("lxc:0") 1704 apiParams[1].ContainerType = instance.LXC 1705 apiParams[2].Placement = instance.MustParsePlacement("dummyenv:invalid") 1706 apiParams[3].Placement = instance.MustParsePlacement("dummyenv:valid") 1707 machines, err := s.APIState.Client().AddMachines(apiParams) 1708 c.Assert(err, gc.IsNil) 1709 c.Assert(len(machines), gc.Equals, 4) 1710 c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0") 1711 c.Assert(machines[1].Error, gc.ErrorMatches, "container type and placement are mutually exclusive") 1712 c.Assert(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: invalid placement is invalid") 1713 c.Assert(machines[3].Machine, gc.Equals, "1") 1714 1715 m, err := s.BackingState.Machine(machines[3].Machine) 1716 c.Assert(err, gc.IsNil) 1717 c.Assert(m.Placement(), gc.DeepEquals, apiParams[3].Placement.Directive) 1718 } 1719 1720 func (s *clientSuite) TestClientAddMachines1dot18(c *gc.C) { 1721 apiParams := make([]params.AddMachineParams, 2) 1722 for i := range apiParams { 1723 apiParams[i] = params.AddMachineParams{ 1724 Jobs: []params.MachineJob{params.JobHostUnits}, 1725 } 1726 } 1727 apiParams[1].ContainerType = instance.LXC 1728 apiParams[1].ParentId = "0" 1729 machines, err := s.APIState.Client().AddMachines1dot18(apiParams) 1730 c.Assert(err, gc.IsNil) 1731 c.Assert(len(machines), gc.Equals, 2) 1732 c.Assert(machines[0].Machine, gc.Equals, "0") 1733 c.Assert(machines[1].Machine, gc.Equals, "0/lxc/0") 1734 } 1735 1736 func (s *clientSuite) TestClientAddMachines1dot18SomeErrors(c *gc.C) { 1737 apiParams := []params.AddMachineParams{{ 1738 Jobs: []params.MachineJob{params.JobHostUnits}, 1739 ParentId: "123", 1740 }} 1741 machines, err := s.APIState.Client().AddMachines1dot18(apiParams) 1742 c.Assert(err, gc.IsNil) 1743 c.Assert(len(machines), gc.Equals, 1) 1744 c.Check(machines[0].Error, gc.ErrorMatches, "parent machine specified without container type") 1745 } 1746 1747 func (s *clientSuite) TestClientAddMachinesSomeErrors(c *gc.C) { 1748 // Here we check that adding a number of containers correctly handles the 1749 // case that some adds succeed and others fail and report the errors 1750 // accordingly. 1751 // We will set up params to the AddMachines API to attempt to create 3 machines. 1752 // Machines 0 and 1 will be added successfully. 1753 // Remaining machines will fail due to different reasons. 1754 1755 // Create a machine to host the requested containers. 1756 host, err := s.State.AddMachine("quantal", state.JobHostUnits) 1757 c.Assert(err, gc.IsNil) 1758 // The host only supports lxc containers. 1759 err = host.SetSupportedContainers([]instance.ContainerType{instance.LXC}) 1760 c.Assert(err, gc.IsNil) 1761 1762 // Set up params for adding 3 containers. 1763 apiParams := make([]params.AddMachineParams, 3) 1764 for i := range apiParams { 1765 apiParams[i] = params.AddMachineParams{ 1766 Jobs: []params.MachineJob{params.JobHostUnits}, 1767 } 1768 } 1769 // This will cause a machine add to fail due to an unsupported container. 1770 apiParams[2].ContainerType = instance.KVM 1771 apiParams[2].ParentId = host.Id() 1772 machines, err := s.APIState.Client().AddMachines(apiParams) 1773 c.Assert(err, gc.IsNil) 1774 c.Assert(len(machines), gc.Equals, 3) 1775 1776 // Check the results - machines 2 and 3 will have errors. 1777 c.Check(machines[0].Machine, gc.Equals, "1") 1778 c.Check(machines[0].Error, gc.IsNil) 1779 c.Check(machines[1].Machine, gc.Equals, "2") 1780 c.Check(machines[1].Error, gc.IsNil) 1781 c.Check(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host kvm containers") 1782 } 1783 1784 func (s *clientSuite) TestClientAddMachinesWithInstanceIdSomeErrors(c *gc.C) { 1785 apiParams := make([]params.AddMachineParams, 3) 1786 addrs := []instance.Address{instance.NewAddress("1.2.3.4", instance.NetworkUnknown)} 1787 hc := instance.MustParseHardware("mem=4G") 1788 for i := 0; i < 3; i++ { 1789 apiParams[i] = params.AddMachineParams{ 1790 Jobs: []params.MachineJob{params.JobHostUnits}, 1791 InstanceId: instance.Id(fmt.Sprintf("1234-%d", i)), 1792 Nonce: "foo", 1793 HardwareCharacteristics: hc, 1794 Addrs: addrs, 1795 } 1796 } 1797 // This will cause the last machine add to fail. 1798 apiParams[2].Nonce = "" 1799 machines, err := s.APIState.Client().AddMachines(apiParams) 1800 c.Assert(err, gc.IsNil) 1801 c.Assert(len(machines), gc.Equals, 3) 1802 for i, machineResult := range machines { 1803 if i == 2 { 1804 c.Assert(machineResult.Error, gc.NotNil) 1805 c.Assert(machineResult.Error, gc.ErrorMatches, "cannot add a new machine: cannot add a machine with an instance id and no nonce") 1806 } else { 1807 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i)) 1808 s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String()) 1809 instanceId := fmt.Sprintf("1234-%d", i) 1810 s.checkInstance(c, machineResult.Machine, instanceId, "foo", hc, addrs) 1811 } 1812 } 1813 } 1814 1815 func (s *clientSuite) checkInstance(c *gc.C, id, instanceId, nonce string, 1816 hc instance.HardwareCharacteristics, addr []instance.Address) { 1817 1818 machine, err := s.BackingState.Machine(id) 1819 c.Assert(err, gc.IsNil) 1820 machineInstanceId, err := machine.InstanceId() 1821 c.Assert(err, gc.IsNil) 1822 c.Assert(machine.CheckProvisioned(nonce), jc.IsTrue) 1823 c.Assert(machineInstanceId, gc.Equals, instance.Id(instanceId)) 1824 machineHardware, err := machine.HardwareCharacteristics() 1825 c.Assert(err, gc.IsNil) 1826 c.Assert(machineHardware.String(), gc.Equals, hc.String()) 1827 c.Assert(machine.Addresses(), gc.DeepEquals, addr) 1828 } 1829 1830 func (s *clientSuite) TestInjectMachinesStillExists(c *gc.C) { 1831 results := new(params.AddMachinesResults) 1832 // We need to use Call directly because the client interface 1833 // no longer refers to InjectMachine. 1834 args := params.AddMachines{ 1835 MachineParams: []params.AddMachineParams{{ 1836 Jobs: []params.MachineJob{params.JobHostUnits}, 1837 InstanceId: "i-foo", 1838 Nonce: "nonce", 1839 }}, 1840 } 1841 err := s.APIState.Call("Client", "", "AddMachines", args, &results) 1842 c.Assert(err, gc.IsNil) 1843 c.Assert(results.Machines, gc.HasLen, 1) 1844 } 1845 1846 func (s *clientSuite) TestProvisioningScript(c *gc.C) { 1847 // Inject a machine and then call the ProvisioningScript API. 1848 // The result should be the same as when calling MachineConfig, 1849 // converting it to a cloudinit.MachineConfig, and disabling 1850 // apt_upgrade. 1851 apiParams := params.AddMachineParams{ 1852 Jobs: []params.MachineJob{params.JobHostUnits}, 1853 InstanceId: instance.Id("1234"), 1854 Nonce: "foo", 1855 HardwareCharacteristics: instance.MustParseHardware("arch=amd64"), 1856 } 1857 machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams}) 1858 c.Assert(err, gc.IsNil) 1859 c.Assert(len(machines), gc.Equals, 1) 1860 machineId := machines[0].Machine 1861 // Call ProvisioningScript. Normally ProvisioningScript and 1862 // MachineConfig are mutually exclusive; both of them will 1863 // allocate a state/api password for the machine agent. 1864 script, err := s.APIState.Client().ProvisioningScript(params.ProvisioningScriptParams{ 1865 MachineId: machineId, 1866 Nonce: apiParams.Nonce, 1867 }) 1868 c.Assert(err, gc.IsNil) 1869 mcfg, err := client.MachineConfig(s.State, machineId, apiParams.Nonce, "") 1870 c.Assert(err, gc.IsNil) 1871 sshinitScript, err := manual.ProvisioningScript(mcfg) 1872 c.Assert(err, gc.IsNil) 1873 // ProvisioningScript internally calls MachineConfig, 1874 // which allocates a new, random password. Everything 1875 // about the scripts should be the same other than 1876 // the line containing "oldpassword" from agent.conf. 1877 scriptLines := strings.Split(script, "\n") 1878 sshinitScriptLines := strings.Split(sshinitScript, "\n") 1879 c.Assert(scriptLines, gc.HasLen, len(sshinitScriptLines)) 1880 for i, line := range scriptLines { 1881 if strings.Contains(line, "oldpassword") { 1882 continue 1883 } 1884 c.Assert(line, gc.Equals, sshinitScriptLines[i]) 1885 } 1886 } 1887 1888 func (s *clientSuite) TestProvisioningScriptDisablePackageCommands(c *gc.C) { 1889 apiParams := params.AddMachineParams{ 1890 Jobs: []params.MachineJob{params.JobHostUnits}, 1891 InstanceId: instance.Id("1234"), 1892 Nonce: "foo", 1893 HardwareCharacteristics: instance.MustParseHardware("arch=amd64"), 1894 } 1895 machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{apiParams}) 1896 c.Assert(err, gc.IsNil) 1897 c.Assert(len(machines), gc.Equals, 1) 1898 machineId := machines[0].Machine 1899 for _, disable := range []bool{false, true} { 1900 script, err := s.APIState.Client().ProvisioningScript(params.ProvisioningScriptParams{ 1901 MachineId: machineId, 1902 Nonce: apiParams.Nonce, 1903 DisablePackageCommands: disable, 1904 }) 1905 c.Assert(err, gc.IsNil) 1906 var checker gc.Checker = jc.Contains 1907 if disable { 1908 // We disabled package commands: there should be no "apt" commands in the script. 1909 checker = gc.Not(checker) 1910 } 1911 c.Assert(script, checker, "apt-get") 1912 } 1913 } 1914 1915 func (s *clientSuite) TestClientSpecializeStoreOnDeployServiceSetCharmAndAddCharm(c *gc.C) { 1916 store, restore := makeMockCharmStore() 1917 defer restore() 1918 1919 attrs := map[string]interface{}{"charm-store-auth": "token=value", 1920 "test-mode": true} 1921 err := s.State.UpdateEnvironConfig(attrs, nil, nil) 1922 c.Assert(err, gc.IsNil) 1923 1924 curl, _ := addCharm(c, store, "dummy") 1925 err = s.APIState.Client().ServiceDeploy( 1926 curl.String(), "service", 3, "", constraints.Value{}, "", 1927 ) 1928 c.Assert(err, gc.IsNil) 1929 1930 // check that the store's auth attributes were set 1931 c.Assert(store.AuthAttrs, gc.Equals, "token=value") 1932 c.Assert(store.TestMode, gc.Equals, true) 1933 1934 store.AuthAttrs = "" 1935 1936 curl, _ = addCharm(c, store, "wordpress") 1937 err = s.APIState.Client().ServiceSetCharm( 1938 "service", curl.String(), false, 1939 ) 1940 1941 // check that the store's auth attributes were set 1942 c.Assert(store.AuthAttrs, gc.Equals, "token=value") 1943 1944 curl, _ = addCharm(c, store, "riak") 1945 err = s.APIState.Client().AddCharm(curl) 1946 1947 // check that the store's auth attributes were set 1948 c.Assert(store.AuthAttrs, gc.Equals, "token=value") 1949 } 1950 1951 func (s *clientSuite) TestAddCharm(c *gc.C) { 1952 store, restore := makeMockCharmStore() 1953 defer restore() 1954 1955 client := s.APIState.Client() 1956 // First test the sanity checks. 1957 err := client.AddCharm(&charm.URL{Reference: charm.Reference{Name: "nonsense"}}) 1958 c.Assert(err, gc.ErrorMatches, `charm URL has invalid schema: ":nonsense-0"`) 1959 err = client.AddCharm(charm.MustParseURL("local:precise/dummy")) 1960 c.Assert(err, gc.ErrorMatches, "only charm store charm URLs are supported, with cs: schema") 1961 err = client.AddCharm(charm.MustParseURL("cs:precise/wordpress")) 1962 c.Assert(err, gc.ErrorMatches, "charm URL must include revision") 1963 1964 // Add a charm, without uploading it to storage, to 1965 // check that AddCharm does not try to do it. 1966 charmDir := charmtesting.Charms.Dir("dummy") 1967 ident := fmt.Sprintf("%s-%d", charmDir.Meta().Name, charmDir.Revision()) 1968 curl := charm.MustParseURL("cs:quantal/" + ident) 1969 bundleURL, err := url.Parse("http://bundles.testing.invalid/" + ident) 1970 c.Assert(err, gc.IsNil) 1971 sch, err := s.State.AddCharm(charmDir, curl, bundleURL, ident+"-sha256") 1972 c.Assert(err, gc.IsNil) 1973 1974 name := charm.Quote(sch.URL().String()) 1975 storage := s.Conn.Environ.Storage() 1976 _, err = storage.Get(name) 1977 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1978 1979 // AddCharm should see the charm in state and not upload it. 1980 err = client.AddCharm(sch.URL()) 1981 c.Assert(err, gc.IsNil) 1982 _, err = storage.Get(name) 1983 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1984 1985 // Now try adding another charm completely. 1986 curl, _ = addCharm(c, store, "wordpress") 1987 err = client.AddCharm(curl) 1988 c.Assert(err, gc.IsNil) 1989 1990 // Verify it's in state and it got uploaded. 1991 sch, err = s.State.Charm(curl) 1992 c.Assert(err, gc.IsNil) 1993 s.assertUploaded(c, storage, sch.BundleURL(), sch.BundleSha256()) 1994 } 1995 1996 var resolveCharmCases = []struct { 1997 schema, defaultSeries, charmName string 1998 parseErr string 1999 resolveErr string 2000 }{ 2001 {"cs", "precise", "wordpress", "", ""}, 2002 {"cs", "trusty", "wordpress", "", ""}, 2003 {"cs", "", "wordpress", "", `missing default series, cannot resolve charm url: "cs:wordpress"`}, 2004 {"cs", "trusty", "", `charm URL has invalid charm name: "cs:"`, ""}, 2005 {"local", "trusty", "wordpress", "", `only charm store charm references are supported, with cs: schema`}, 2006 {"cs", "precise", "hl3", "", ""}, 2007 {"cs", "trusty", "hl3", "", ""}, 2008 {"cs", "", "hl3", "", `missing default series, cannot resolve charm url: \"cs:hl3\"`}, 2009 } 2010 2011 func (s *clientSuite) TestResolveCharm(c *gc.C) { 2012 store, restore := makeMockCharmStore() 2013 defer restore() 2014 2015 for i, test := range resolveCharmCases { 2016 c.Logf("test %d: %#v", i, test) 2017 // Mock charm store will use this to resolve a charm reference. 2018 store.DefaultSeries = test.defaultSeries 2019 2020 client := s.APIState.Client() 2021 ref, series, err := charm.ParseReference(fmt.Sprintf("%s:%s", test.schema, test.charmName)) 2022 if test.parseErr == "" { 2023 if !c.Check(err, gc.IsNil) { 2024 continue 2025 } 2026 } else { 2027 c.Assert(err, gc.NotNil) 2028 c.Check(err, gc.ErrorMatches, test.parseErr) 2029 continue 2030 } 2031 c.Check(series, gc.Equals, "") 2032 c.Check(ref.String(), gc.Equals, fmt.Sprintf("%s:%s", test.schema, test.charmName)) 2033 2034 curl, err := client.ResolveCharm(ref) 2035 if err == nil { 2036 c.Assert(curl, gc.NotNil) 2037 // Only cs: schema should make it through here 2038 c.Check(curl.String(), gc.Equals, fmt.Sprintf("cs:%s/%s", test.defaultSeries, test.charmName)) 2039 c.Check(test.resolveErr, gc.Equals, "") 2040 } else { 2041 c.Check(curl, gc.IsNil) 2042 c.Check(err, gc.ErrorMatches, test.resolveErr) 2043 } 2044 } 2045 } 2046 2047 func (s *clientSuite) TestAddCharmConcurrently(c *gc.C) { 2048 store, restore := makeMockCharmStore() 2049 defer restore() 2050 2051 client := s.APIState.Client() 2052 curl, _ := addCharm(c, store, "wordpress") 2053 2054 // Expect storage Put() to be called once for each goroutine 2055 // below. 2056 ops := make(chan dummy.Operation, 500) 2057 dummy.Listen(ops) 2058 go s.assertPutCalled(c, ops, 10) 2059 2060 // Try adding the same charm concurrently from multiple goroutines 2061 // to test no "duplicate key errors" are reported (see lp bug 2062 // #1067979) and also at the end only one charm document is 2063 // created. 2064 2065 var wg sync.WaitGroup 2066 for i := 0; i < 10; i++ { 2067 wg.Add(1) 2068 go func(index int) { 2069 defer wg.Done() 2070 2071 c.Assert(client.AddCharm(curl), gc.IsNil, gc.Commentf("goroutine %d", index)) 2072 sch, err := s.State.Charm(curl) 2073 c.Assert(err, gc.IsNil, gc.Commentf("goroutine %d", index)) 2074 c.Assert(sch.URL(), jc.DeepEquals, curl, gc.Commentf("goroutine %d", index)) 2075 expectedName := fmt.Sprintf("%s-%d-[0-9a-f-]+", curl.Name, curl.Revision) 2076 c.Assert(getArchiveName(sch.BundleURL()), gc.Matches, expectedName) 2077 }(i) 2078 } 2079 wg.Wait() 2080 close(ops) 2081 2082 // Verify there is only a single uploaded charm remains and it 2083 // contains the correct data. 2084 sch, err := s.State.Charm(curl) 2085 c.Assert(err, gc.IsNil) 2086 storage, err := environs.GetStorage(s.State) 2087 c.Assert(err, gc.IsNil) 2088 uploads, err := storage.List(fmt.Sprintf("%s-%d-", curl.Name, curl.Revision)) 2089 c.Assert(err, gc.IsNil) 2090 c.Assert(uploads, gc.HasLen, 1) 2091 c.Assert(getArchiveName(sch.BundleURL()), gc.Equals, uploads[0]) 2092 s.assertUploaded(c, storage, sch.BundleURL(), sch.BundleSha256()) 2093 } 2094 2095 func (s *clientSuite) TestAddCharmOverwritesPlaceholders(c *gc.C) { 2096 store, restore := makeMockCharmStore() 2097 defer restore() 2098 2099 client := s.APIState.Client() 2100 curl, _ := addCharm(c, store, "wordpress") 2101 2102 // Add a placeholder with the same charm URL. 2103 err := s.State.AddStoreCharmPlaceholder(curl) 2104 c.Assert(err, gc.IsNil) 2105 _, err = s.State.Charm(curl) 2106 c.Assert(err, jc.Satisfies, errors.IsNotFound) 2107 2108 // Now try to add the charm, which will convert the placeholder to 2109 // a pending charm. 2110 err = client.AddCharm(curl) 2111 c.Assert(err, gc.IsNil) 2112 2113 // Make sure the document's flags were reset as expected. 2114 sch, err := s.State.Charm(curl) 2115 c.Assert(err, gc.IsNil) 2116 c.Assert(sch.URL(), jc.DeepEquals, curl) 2117 c.Assert(sch.IsPlaceholder(), jc.IsFalse) 2118 c.Assert(sch.IsUploaded(), jc.IsTrue) 2119 } 2120 2121 func (s *clientSuite) TestCharmArchiveName(c *gc.C) { 2122 for rev, name := range []string{"Foo", "bar", "wordpress", "mysql"} { 2123 archiveFormat := fmt.Sprintf("%s-%d-[0-9a-f-]+", name, rev) 2124 archiveName, err := client.CharmArchiveName(name, rev) 2125 c.Check(err, gc.IsNil) 2126 c.Check(archiveName, gc.Matches, archiveFormat) 2127 } 2128 } 2129 2130 func (s *clientSuite) assertPutCalled(c *gc.C, ops chan dummy.Operation, numCalls int) { 2131 calls := 0 2132 select { 2133 case op, ok := <-ops: 2134 if !ok { 2135 return 2136 } 2137 if op, ok := op.(dummy.OpPutFile); ok { 2138 calls++ 2139 if calls > numCalls { 2140 c.Fatalf("storage Put() called %d times, expected %d times", calls, numCalls) 2141 return 2142 } 2143 nameFormat := "[0-9a-z-]+-[0-9]+-[0-9a-f-]+" 2144 c.Assert(op.FileName, gc.Matches, nameFormat) 2145 } 2146 case <-time.After(coretesting.LongWait): 2147 c.Fatalf("timed out while waiting for a storage Put() calls") 2148 return 2149 } 2150 } 2151 2152 func (s *clientSuite) assertUploaded(c *gc.C, storage envstorage.Storage, bundleURL *url.URL, expectedSHA256 string) { 2153 archiveName := getArchiveName(bundleURL) 2154 reader, err := storage.Get(archiveName) 2155 c.Assert(err, gc.IsNil) 2156 defer reader.Close() 2157 downloadedSHA256, _, err := utils.ReadSHA256(reader) 2158 c.Assert(err, gc.IsNil) 2159 c.Assert(downloadedSHA256, gc.Equals, expectedSHA256) 2160 } 2161 2162 func getArchiveName(bundleURL *url.URL) string { 2163 return strings.TrimPrefix(bundleURL.RequestURI(), "/dummyenv/private/") 2164 } 2165 2166 func (s *clientSuite) TestRetryProvisioning(c *gc.C) { 2167 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 2168 c.Assert(err, gc.IsNil) 2169 err = machine.SetStatus(params.StatusError, "error", nil) 2170 c.Assert(err, gc.IsNil) 2171 _, err = s.APIState.Client().RetryProvisioning(machine.Tag()) 2172 c.Assert(err, gc.IsNil) 2173 2174 status, info, data, err := machine.Status() 2175 c.Assert(err, gc.IsNil) 2176 c.Assert(status, gc.Equals, params.StatusError) 2177 c.Assert(info, gc.Equals, "error") 2178 c.Assert(data["transient"], gc.Equals, true) 2179 } 2180 2181 func (s *clientSuite) setAgentAlive(c *gc.C, machineId string) *presence.Pinger { 2182 m, err := s.BackingState.Machine(machineId) 2183 c.Assert(err, gc.IsNil) 2184 pinger, err := m.SetAgentAlive() 2185 c.Assert(err, gc.IsNil) 2186 s.BackingState.StartSync() 2187 err = m.WaitAgentAlive(coretesting.LongWait) 2188 c.Assert(err, gc.IsNil) 2189 return pinger 2190 } 2191 2192 var ( 2193 emptyCons = constraints.Value{} 2194 defaultSeries = "" 2195 ) 2196 2197 func (s *clientSuite) TestClientEnsureAvailabilitySeries(c *gc.C) { 2198 _, err := s.State.AddMachine("quantal", state.JobManageEnviron) 2199 c.Assert(err, gc.IsNil) 2200 // We have to ensure the agents are alive, or EnsureAvailability will 2201 // create more to replace them. 2202 pinger := s.setAgentAlive(c, "0") 2203 defer pinger.Kill() 2204 machines, err := s.State.AllMachines() 2205 c.Assert(err, gc.IsNil) 2206 c.Assert(machines, gc.HasLen, 1) 2207 c.Assert(machines[0].Series(), gc.Equals, "quantal") 2208 err = s.APIState.Client().EnsureAvailability(3, emptyCons, defaultSeries) 2209 c.Assert(err, gc.IsNil) 2210 machines, err = s.State.AllMachines() 2211 c.Assert(err, gc.IsNil) 2212 c.Assert(machines, gc.HasLen, 3) 2213 c.Assert(machines[0].Series(), gc.Equals, "quantal") 2214 c.Assert(machines[1].Series(), gc.Equals, "quantal") 2215 c.Assert(machines[2].Series(), gc.Equals, "quantal") 2216 defer s.setAgentAlive(c, "1").Kill() 2217 defer s.setAgentAlive(c, "2").Kill() 2218 err = s.APIState.Client().EnsureAvailability(5, emptyCons, "non-default") 2219 c.Assert(err, gc.IsNil) 2220 machines, err = s.State.AllMachines() 2221 c.Assert(err, gc.IsNil) 2222 c.Assert(machines, gc.HasLen, 5) 2223 c.Assert(machines[0].Series(), gc.Equals, "quantal") 2224 c.Assert(machines[1].Series(), gc.Equals, "quantal") 2225 c.Assert(machines[2].Series(), gc.Equals, "quantal") 2226 c.Assert(machines[3].Series(), gc.Equals, "non-default") 2227 c.Assert(machines[4].Series(), gc.Equals, "non-default") 2228 } 2229 2230 func (s *clientSuite) TestClientEnsureAvailabilityConstraints(c *gc.C) { 2231 _, err := s.State.AddMachine("quantal", state.JobManageEnviron) 2232 c.Assert(err, gc.IsNil) 2233 pinger := s.setAgentAlive(c, "0") 2234 defer pinger.Kill() 2235 err = s.APIState.Client().EnsureAvailability( 2236 3, constraints.MustParse("mem=4G"), defaultSeries) 2237 c.Assert(err, gc.IsNil) 2238 machines, err := s.State.AllMachines() 2239 c.Assert(err, gc.IsNil) 2240 c.Assert(machines, gc.HasLen, 3) 2241 expectedCons := []constraints.Value{ 2242 constraints.Value{}, 2243 constraints.MustParse("mem=4G"), 2244 constraints.MustParse("mem=4G"), 2245 } 2246 for i, m := range machines { 2247 cons, err := m.Constraints() 2248 c.Assert(err, gc.IsNil) 2249 c.Check(cons, gc.DeepEquals, expectedCons[i]) 2250 } 2251 } 2252 2253 func (s *clientSuite) TestClientEnsureAvailability0Preserves(c *gc.C) { 2254 _, err := s.State.AddMachine("quantal", state.JobManageEnviron) 2255 c.Assert(err, gc.IsNil) 2256 pinger := s.setAgentAlive(c, "0") 2257 defer pinger.Kill() 2258 // A value of 0 says either "if I'm not HA, make me HA" or "preserve my 2259 // current HA settings". 2260 err = s.APIState.Client().EnsureAvailability(0, emptyCons, defaultSeries) 2261 c.Assert(err, gc.IsNil) 2262 machines, err := s.State.AllMachines() 2263 c.Assert(machines, gc.HasLen, 3) 2264 defer s.setAgentAlive(c, "1").Kill() 2265 // Now, we keep agent 1 alive, but not agent 2, calling 2266 // EnsureAvailability(0) again will cause us to start another machine 2267 err = s.APIState.Client().EnsureAvailability(0, emptyCons, defaultSeries) 2268 c.Assert(err, gc.IsNil) 2269 machines, err = s.State.AllMachines() 2270 c.Assert(machines, gc.HasLen, 4) 2271 } 2272 2273 func (s *clientSuite) TestClientEnsureAvailability0Preserves5(c *gc.C) { 2274 _, err := s.State.AddMachine("quantal", state.JobManageEnviron) 2275 c.Assert(err, gc.IsNil) 2276 pinger := s.setAgentAlive(c, "0") 2277 defer pinger.Kill() 2278 // Start off with 5 servers 2279 err = s.APIState.Client().EnsureAvailability(5, emptyCons, defaultSeries) 2280 c.Assert(err, gc.IsNil) 2281 machines, err := s.State.AllMachines() 2282 c.Assert(machines, gc.HasLen, 5) 2283 defer s.setAgentAlive(c, "1").Kill() 2284 defer s.setAgentAlive(c, "2").Kill() 2285 defer s.setAgentAlive(c, "3").Kill() 2286 // Keeping all alive but one, will bring up 1 more server to preserve 5 2287 err = s.APIState.Client().EnsureAvailability(0, emptyCons, defaultSeries) 2288 c.Assert(err, gc.IsNil) 2289 machines, err = s.State.AllMachines() 2290 c.Assert(machines, gc.HasLen, 6) 2291 } 2292 2293 func (s *clientSuite) TestClientEnsureAvailabilityErrors(c *gc.C) { 2294 _, err := s.State.AddMachine("quantal", state.JobManageEnviron) 2295 c.Assert(err, gc.IsNil) 2296 pinger := s.setAgentAlive(c, "0") 2297 defer pinger.Kill() 2298 err = s.APIState.Client().EnsureAvailability(-1, emptyCons, defaultSeries) 2299 c.Assert(err, gc.ErrorMatches, "number of state servers must be odd and non-negative") 2300 err = s.APIState.Client().EnsureAvailability(3, emptyCons, defaultSeries) 2301 c.Assert(err, gc.IsNil) 2302 err = s.APIState.Client().EnsureAvailability(1, emptyCons, defaultSeries) 2303 c.Assert(err, gc.ErrorMatches, "cannot reduce state server count") 2304 } 2305 2306 func (s *clientSuite) TestAPIHostPorts(c *gc.C) { 2307 apiHostPorts, err := s.APIState.Client().APIHostPorts() 2308 c.Assert(err, gc.IsNil) 2309 c.Assert(apiHostPorts, gc.HasLen, 0) 2310 2311 server1Addresses := []instance.Address{{ 2312 Value: "server-1", 2313 Type: instance.HostName, 2314 NetworkScope: instance.NetworkPublic, 2315 }, { 2316 Value: "10.0.0.1", 2317 Type: instance.Ipv4Address, 2318 NetworkName: "internal", 2319 NetworkScope: instance.NetworkCloudLocal, 2320 }} 2321 server2Addresses := []instance.Address{{ 2322 Value: "::1", 2323 Type: instance.Ipv6Address, 2324 NetworkName: "loopback", 2325 NetworkScope: instance.NetworkMachineLocal, 2326 }} 2327 stateAPIHostPorts := [][]instance.HostPort{ 2328 instance.AddressesWithPort(server1Addresses, 123), 2329 instance.AddressesWithPort(server2Addresses, 456), 2330 } 2331 2332 err = s.State.SetAPIHostPorts(stateAPIHostPorts) 2333 c.Assert(err, gc.IsNil) 2334 apiHostPorts, err = s.APIState.Client().APIHostPorts() 2335 c.Assert(err, gc.IsNil) 2336 c.Assert(apiHostPorts, gc.DeepEquals, stateAPIHostPorts) 2337 } 2338 2339 func (s *clientSuite) TestClientAgentVersion(c *gc.C) { 2340 current := version.MustParse("1.2.0") 2341 s.PatchValue(&version.Current.Number, current) 2342 result, err := s.APIState.Client().AgentVersion() 2343 c.Assert(err, gc.IsNil) 2344 c.Assert(result, gc.Equals, current) 2345 }