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