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