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