github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/service/service_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package service_test 5 6 import ( 7 "fmt" 8 "io" 9 "regexp" 10 "sync" 11 12 "github.com/juju/errors" 13 "github.com/juju/names" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/charm.v6-unstable" 18 "gopkg.in/juju/charmrepo.v2-unstable" 19 "gopkg.in/juju/charmrepo.v2-unstable/csclient" 20 csparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" 21 "gopkg.in/macaroon.v1" 22 "gopkg.in/mgo.v2" 23 24 commontesting "github.com/juju/juju/apiserver/common/testing" 25 "github.com/juju/juju/apiserver/params" 26 "github.com/juju/juju/apiserver/service" 27 apiservertesting "github.com/juju/juju/apiserver/testing" 28 "github.com/juju/juju/constraints" 29 "github.com/juju/juju/instance" 30 jujutesting "github.com/juju/juju/juju/testing" 31 "github.com/juju/juju/state" 32 statestorage "github.com/juju/juju/state/storage" 33 "github.com/juju/juju/status" 34 "github.com/juju/juju/storage" 35 "github.com/juju/juju/storage/poolmanager" 36 "github.com/juju/juju/storage/provider" 37 "github.com/juju/juju/storage/provider/registry" 38 "github.com/juju/juju/testcharms" 39 "github.com/juju/juju/testing/factory" 40 jujuversion "github.com/juju/juju/version" 41 ) 42 43 type serviceSuite struct { 44 jujutesting.JujuConnSuite 45 apiservertesting.CharmStoreSuite 46 commontesting.BlockHelper 47 48 serviceApi *service.API 49 service *state.Service 50 authorizer apiservertesting.FakeAuthorizer 51 } 52 53 var _ = gc.Suite(&serviceSuite{}) 54 55 var _ service.Service = (*service.API)(nil) 56 57 func (s *serviceSuite) SetUpSuite(c *gc.C) { 58 s.CharmStoreSuite.SetUpSuite(c) 59 s.JujuConnSuite.SetUpSuite(c) 60 } 61 62 func (s *serviceSuite) TearDownSuite(c *gc.C) { 63 s.CharmStoreSuite.TearDownSuite(c) 64 s.JujuConnSuite.TearDownSuite(c) 65 } 66 67 func (s *serviceSuite) SetUpTest(c *gc.C) { 68 s.JujuConnSuite.SetUpTest(c) 69 s.CharmStoreSuite.Session = s.JujuConnSuite.Session 70 s.CharmStoreSuite.SetUpTest(c) 71 s.BlockHelper = commontesting.NewBlockHelper(s.APIState) 72 s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() }) 73 74 s.service = s.Factory.MakeService(c, nil) 75 76 s.authorizer = apiservertesting.FakeAuthorizer{ 77 Tag: s.AdminUserTag(c), 78 } 79 var err error 80 s.serviceApi, err = service.NewAPI(s.State, nil, s.authorizer) 81 c.Assert(err, jc.ErrorIsNil) 82 } 83 84 func (s *serviceSuite) TearDownTest(c *gc.C) { 85 s.CharmStoreSuite.TearDownTest(c) 86 s.JujuConnSuite.TearDownTest(c) 87 } 88 89 func (s *serviceSuite) TestSetMetricCredentials(c *gc.C) { 90 charm := s.Factory.MakeCharm(c, &factory.CharmParams{Name: "wordpress"}) 91 wordpress := s.Factory.MakeService(c, &factory.ServiceParams{ 92 Charm: charm, 93 }) 94 tests := []struct { 95 about string 96 args params.ServiceMetricCredentials 97 results params.ErrorResults 98 }{ 99 { 100 "test one argument and it passes", 101 params.ServiceMetricCredentials{[]params.ServiceMetricCredential{{ 102 s.service.Name(), 103 []byte("creds 1234"), 104 }}}, 105 params.ErrorResults{[]params.ErrorResult{{Error: nil}}}, 106 }, 107 { 108 "test two arguments and both pass", 109 params.ServiceMetricCredentials{[]params.ServiceMetricCredential{ 110 { 111 s.service.Name(), 112 []byte("creds 1234"), 113 }, 114 { 115 wordpress.Name(), 116 []byte("creds 4567"), 117 }, 118 }}, 119 params.ErrorResults{[]params.ErrorResult{ 120 {Error: nil}, 121 {Error: nil}, 122 }}, 123 }, 124 { 125 "test two arguments and second one fails", 126 params.ServiceMetricCredentials{[]params.ServiceMetricCredential{ 127 { 128 s.service.Name(), 129 []byte("creds 1234"), 130 }, 131 { 132 "not-a-service", 133 []byte("creds 4567"), 134 }, 135 }}, 136 params.ErrorResults{[]params.ErrorResult{ 137 {Error: nil}, 138 {Error: ¶ms.Error{Message: `service "not-a-service" not found`, Code: "not found"}}, 139 }}, 140 }, 141 } 142 for i, t := range tests { 143 c.Logf("Running test %d %v", i, t.about) 144 results, err := s.serviceApi.SetMetricCredentials(t.args) 145 c.Assert(err, jc.ErrorIsNil) 146 c.Assert(results.Results, gc.HasLen, len(t.results.Results)) 147 c.Assert(results, gc.DeepEquals, t.results) 148 149 for i, a := range t.args.Creds { 150 if t.results.Results[i].Error == nil { 151 svc, err := s.State.Service(a.ServiceName) 152 c.Assert(err, jc.ErrorIsNil) 153 creds := svc.MetricCredentials() 154 c.Assert(creds, gc.DeepEquals, a.MetricCredentials) 155 } 156 } 157 } 158 } 159 160 func (s *serviceSuite) TestCompatibleSettingsParsing(c *gc.C) { 161 // Test the exported settings parsing in a compatible way. 162 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 163 svc, err := s.State.Service("dummy") 164 c.Assert(err, jc.ErrorIsNil) 165 ch, _, err := svc.Charm() 166 c.Assert(err, jc.ErrorIsNil) 167 c.Assert(ch.URL().String(), gc.Equals, "local:quantal/dummy-1") 168 169 // Empty string will be returned as nil. 170 options := map[string]string{ 171 "title": "foobar", 172 "username": "", 173 } 174 settings, err := service.ParseSettingsCompatible(ch, options) 175 c.Assert(err, jc.ErrorIsNil) 176 c.Assert(settings, gc.DeepEquals, charm.Settings{ 177 "title": "foobar", 178 "username": nil, 179 }) 180 181 // Illegal settings lead to an error. 182 options = map[string]string{ 183 "yummy": "didgeridoo", 184 } 185 _, err = service.ParseSettingsCompatible(ch, options) 186 c.Assert(err, gc.ErrorMatches, `unknown option "yummy"`) 187 } 188 189 func setupStoragePool(c *gc.C, st *state.State) { 190 pm := poolmanager.New(state.NewStateSettings(st)) 191 _, err := pm.Create("loop-pool", provider.LoopProviderType, map[string]interface{}{}) 192 c.Assert(err, jc.ErrorIsNil) 193 err = st.UpdateModelConfig(map[string]interface{}{ 194 "storage-default-block-source": "loop-pool", 195 }, nil, nil) 196 c.Assert(err, jc.ErrorIsNil) 197 } 198 199 func (s *serviceSuite) TestServiceDeployWithStorage(c *gc.C) { 200 setupStoragePool(c, s.State) 201 curl, ch := s.UploadCharm(c, "utopic/storage-block-10", "storage-block") 202 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 203 URL: curl.String(), 204 }) 205 c.Assert(err, jc.ErrorIsNil) 206 storageConstraints := map[string]storage.Constraints{ 207 "data": { 208 Count: 1, 209 Size: 1024, 210 Pool: "loop-pool", 211 }, 212 } 213 214 var cons constraints.Value 215 args := params.ServiceDeploy{ 216 ServiceName: "service", 217 CharmUrl: curl.String(), 218 NumUnits: 1, 219 Constraints: cons, 220 Storage: storageConstraints, 221 } 222 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 223 Services: []params.ServiceDeploy{args}}, 224 ) 225 c.Assert(err, jc.ErrorIsNil) 226 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 227 Results: []params.ErrorResult{{Error: nil}}, 228 }) 229 svc := apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "service", curl, false, ch, cons) 230 storageConstraintsOut, err := svc.StorageConstraints() 231 c.Assert(err, jc.ErrorIsNil) 232 c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{ 233 "data": { 234 Count: 1, 235 Size: 1024, 236 Pool: "loop-pool", 237 }, 238 "allecto": { 239 Count: 0, 240 Size: 1024, 241 Pool: "loop", 242 }, 243 }) 244 } 245 246 func (s *serviceSuite) TestMinJujuVersionTooHigh(c *gc.C) { 247 curl, _ := s.UploadCharm(c, "quantal/minjujuversion-0", "minjujuversion") 248 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 249 URL: curl.String(), 250 }) 251 match := fmt.Sprintf(`charm's min version (999.999.999) is higher than this juju environment's version (%s)`, jujuversion.Current) 252 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(match)) 253 } 254 255 func (s *serviceSuite) TestServiceDeployWithInvalidStoragePool(c *gc.C) { 256 setupStoragePool(c, s.State) 257 curl, _ := s.UploadCharm(c, "utopic/storage-block-0", "storage-block") 258 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 259 URL: curl.String(), 260 }) 261 c.Assert(err, jc.ErrorIsNil) 262 storageConstraints := map[string]storage.Constraints{ 263 "data": storage.Constraints{ 264 Pool: "foo", 265 Count: 1, 266 Size: 1024, 267 }, 268 } 269 270 var cons constraints.Value 271 args := params.ServiceDeploy{ 272 ServiceName: "service", 273 CharmUrl: curl.String(), 274 NumUnits: 1, 275 Constraints: cons, 276 Storage: storageConstraints, 277 } 278 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 279 Services: []params.ServiceDeploy{args}}, 280 ) 281 c.Assert(err, jc.ErrorIsNil) 282 c.Assert(results.Results, gc.HasLen, 1) 283 c.Assert(results.Results[0].Error, gc.ErrorMatches, `.* pool "foo" not found`) 284 } 285 286 func (s *serviceSuite) TestServiceDeployWithUnsupportedStoragePool(c *gc.C) { 287 registry.RegisterProvider("hostloop", &mockStorageProvider{kind: storage.StorageKindBlock}) 288 pm := poolmanager.New(state.NewStateSettings(s.State)) 289 _, err := pm.Create("host-loop-pool", provider.HostLoopProviderType, map[string]interface{}{}) 290 c.Assert(err, jc.ErrorIsNil) 291 292 curl, _ := s.UploadCharm(c, "utopic/storage-block-0", "storage-block") 293 err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 294 URL: curl.String(), 295 }) 296 c.Assert(err, jc.ErrorIsNil) 297 storageConstraints := map[string]storage.Constraints{ 298 "data": storage.Constraints{ 299 Pool: "host-loop-pool", 300 Count: 1, 301 Size: 1024, 302 }, 303 } 304 305 var cons constraints.Value 306 args := params.ServiceDeploy{ 307 ServiceName: "service", 308 CharmUrl: curl.String(), 309 NumUnits: 1, 310 Constraints: cons, 311 Storage: storageConstraints, 312 } 313 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 314 Services: []params.ServiceDeploy{args}}, 315 ) 316 c.Assert(err, jc.ErrorIsNil) 317 c.Assert(results.Results, gc.HasLen, 1) 318 c.Assert(results.Results[0].Error, gc.ErrorMatches, 319 `.*pool "host-loop-pool" uses storage provider "hostloop" which is not supported for models of type "dummy"`) 320 } 321 322 func (s *serviceSuite) TestServiceDeployDefaultFilesystemStorage(c *gc.C) { 323 setupStoragePool(c, s.State) 324 curl, ch := s.UploadCharm(c, "trusty/storage-filesystem-1", "storage-filesystem") 325 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 326 URL: curl.String(), 327 }) 328 c.Assert(err, jc.ErrorIsNil) 329 var cons constraints.Value 330 args := params.ServiceDeploy{ 331 ServiceName: "service", 332 CharmUrl: curl.String(), 333 NumUnits: 1, 334 Constraints: cons, 335 } 336 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 337 Services: []params.ServiceDeploy{args}}, 338 ) 339 c.Assert(err, jc.ErrorIsNil) 340 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 341 Results: []params.ErrorResult{{Error: nil}}, 342 }) 343 svc := apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "service", curl, false, ch, cons) 344 storageConstraintsOut, err := svc.StorageConstraints() 345 c.Assert(err, jc.ErrorIsNil) 346 c.Assert(storageConstraintsOut, gc.DeepEquals, map[string]state.StorageConstraints{ 347 "data": { 348 Count: 1, 349 Size: 1024, 350 Pool: "rootfs", 351 }, 352 }) 353 } 354 355 func (s *serviceSuite) TestServiceDeploy(c *gc.C) { 356 curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy") 357 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 358 URL: curl.String(), 359 }) 360 c.Assert(err, jc.ErrorIsNil) 361 var cons constraints.Value 362 args := params.ServiceDeploy{ 363 ServiceName: "service", 364 CharmUrl: curl.String(), 365 NumUnits: 1, 366 Constraints: cons, 367 Placement: []*instance.Placement{ 368 {"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"}, 369 }, 370 } 371 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 372 Services: []params.ServiceDeploy{args}}, 373 ) 374 c.Assert(err, jc.ErrorIsNil) 375 c.Assert(results, gc.DeepEquals, params.ErrorResults{ 376 Results: []params.ErrorResult{{Error: nil}}, 377 }) 378 svc := apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "service", curl, false, ch, cons) 379 units, err := svc.AllUnits() 380 c.Assert(err, jc.ErrorIsNil) 381 c.Assert(units, gc.HasLen, 1) 382 } 383 384 func (s *serviceSuite) TestServiceDeployWithInvalidPlacement(c *gc.C) { 385 curl, _ := s.UploadCharm(c, "precise/dummy-42", "dummy") 386 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 387 URL: curl.String(), 388 }) 389 c.Assert(err, jc.ErrorIsNil) 390 var cons constraints.Value 391 args := params.ServiceDeploy{ 392 ServiceName: "service", 393 CharmUrl: curl.String(), 394 NumUnits: 1, 395 Constraints: cons, 396 Placement: []*instance.Placement{ 397 {"deadbeef-0bad-400d-8000-4b1d0d06f00d", "invalid"}, 398 }, 399 } 400 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 401 Services: []params.ServiceDeploy{args}}, 402 ) 403 c.Assert(err, jc.ErrorIsNil) 404 c.Assert(results.Results, gc.HasLen, 1) 405 c.Assert(results.Results[0].Error, gc.NotNil) 406 c.Assert(results.Results[0].Error.Error(), gc.Matches, ".* invalid placement is invalid") 407 } 408 409 func (s *serviceSuite) testClientServicesDeployWithBindings(c *gc.C, endpointBindings, expected map[string]string) { 410 curl, _ := s.UploadCharm(c, "utopic/riak-42", "riak") 411 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 412 URL: curl.String(), 413 }) 414 c.Assert(err, jc.ErrorIsNil) 415 416 var cons constraints.Value 417 args := params.ServiceDeploy{ 418 ServiceName: "service", 419 CharmUrl: curl.String(), 420 NumUnits: 1, 421 Constraints: cons, 422 EndpointBindings: endpointBindings, 423 } 424 425 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 426 Services: []params.ServiceDeploy{args}}, 427 ) 428 c.Assert(err, jc.ErrorIsNil) 429 c.Assert(results.Results, gc.HasLen, 1) 430 c.Assert(results.Results[0].Error, gc.IsNil) 431 432 service, err := s.State.Service(args.ServiceName) 433 c.Assert(err, jc.ErrorIsNil) 434 435 retrievedBindings, err := service.EndpointBindings() 436 c.Assert(err, jc.ErrorIsNil) 437 c.Assert(retrievedBindings, jc.DeepEquals, expected) 438 } 439 440 func (s *serviceSuite) TestClientServicesDeployWithBindings(c *gc.C) { 441 s.State.AddSpace("a-space", "", nil, true) 442 expected := map[string]string{ 443 "endpoint": "a-space", 444 "ring": "", 445 "admin": "", 446 } 447 endpointBindings := map[string]string{"endpoint": "a-space"} 448 s.testClientServicesDeployWithBindings(c, endpointBindings, expected) 449 } 450 451 func (s *serviceSuite) TestClientServicesDeployWithDefaultBindings(c *gc.C) { 452 expected := map[string]string{ 453 "endpoint": "", 454 "ring": "", 455 "admin": "", 456 } 457 s.testClientServicesDeployWithBindings(c, nil, expected) 458 } 459 460 // TODO(wallyworld) - the following charm tests have been moved from the apiserver/client 461 // package in order to use the fake charm store testing infrastructure. They are legacy tests 462 // written to use the api client instead of the apiserver logic. They need to be rewritten and 463 // feature tests added. 464 465 func (s *serviceSuite) TestAddCharm(c *gc.C) { 466 var blobs blobs 467 s.PatchValue(service.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage { 468 storage := statestorage.NewStorage(uuid, session) 469 return &recordingStorage{Storage: storage, blobs: &blobs} 470 }) 471 472 client := s.APIState.Client() 473 // First test the sanity checks. 474 err := client.AddCharm(&charm.URL{Name: "nonsense"}, csparams.StableChannel) 475 c.Assert(err, gc.ErrorMatches, `cannot parse charm or bundle URL: ":nonsense-0"`) 476 err = client.AddCharm(charm.MustParseURL("local:precise/dummy"), csparams.StableChannel) 477 c.Assert(err, gc.ErrorMatches, "only charm store charm URLs are supported, with cs: schema") 478 err = client.AddCharm(charm.MustParseURL("cs:precise/wordpress"), csparams.StableChannel) 479 c.Assert(err, gc.ErrorMatches, "charm URL must include revision") 480 481 // Add a charm, without uploading it to storage, to 482 // check that AddCharm does not try to do it. 483 charmDir := testcharms.Repo.CharmDir("dummy") 484 ident := fmt.Sprintf("%s-%d", charmDir.Meta().Name, charmDir.Revision()) 485 curl := charm.MustParseURL("cs:quantal/" + ident) 486 info := state.CharmInfo{ 487 Charm: charmDir, 488 ID: curl, 489 StoragePath: "", 490 SHA256: ident + "-sha256", 491 } 492 sch, err := s.State.AddCharm(info) 493 c.Assert(err, jc.ErrorIsNil) 494 495 // AddCharm should see the charm in state and not upload it. 496 err = client.AddCharm(sch.URL(), csparams.StableChannel) 497 c.Assert(err, jc.ErrorIsNil) 498 499 c.Assert(blobs.m, gc.HasLen, 0) 500 501 // Now try adding another charm completely. 502 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 503 err = client.AddCharm(curl, csparams.StableChannel) 504 c.Assert(err, jc.ErrorIsNil) 505 506 // Verify it's in state and it got uploaded. 507 storage := statestorage.NewStorage(s.State.ModelUUID(), s.State.MongoSession()) 508 sch, err = s.State.Charm(curl) 509 c.Assert(err, jc.ErrorIsNil) 510 s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256()) 511 } 512 513 func (s *serviceSuite) TestAddCharmWithAuthorization(c *gc.C) { 514 // Upload a new charm to the charm store. 515 curl, _ := s.UploadCharm(c, "cs:~restricted/precise/wordpress-3", "wordpress") 516 517 // Change permissions on the new charm such that only bob 518 // can read from it. 519 s.DischargeUser = "restricted" 520 err := s.Client.Put("/"+curl.Path()+"/meta/perm/read", []string{"bob"}) 521 c.Assert(err, jc.ErrorIsNil) 522 523 // Try to add a charm to the environment without authorization. 524 s.DischargeUser = "" 525 err = s.APIState.Client().AddCharm(curl, csparams.StableChannel) 526 c.Assert(err, gc.ErrorMatches, `cannot retrieve charm "cs:~restricted/precise/wordpress-3": cannot get archive: cannot get discharge from "https://.*": third party refused discharge: cannot discharge: discharge denied \(unauthorized access\)`) 527 528 tryAs := func(user string) error { 529 client := csclient.New(csclient.Params{ 530 URL: s.Srv.URL, 531 }) 532 s.DischargeUser = user 533 var m *macaroon.Macaroon 534 err = client.Get("/delegatable-macaroon", &m) 535 c.Assert(err, gc.IsNil) 536 537 return service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 538 URL: curl.String(), 539 Channel: string(csparams.StableChannel), 540 }) 541 } 542 // Try again with authorization for the wrong user. 543 err = tryAs("joe") 544 c.Assert(err, gc.ErrorMatches, `cannot retrieve charm "cs:~restricted/precise/wordpress-3": cannot get archive: unauthorized: access denied for user "joe"`) 545 546 // Try again with the correct authorization this time. 547 err = tryAs("bob") 548 c.Assert(err, gc.IsNil) 549 550 // Verify that it has actually been uploaded. 551 _, err = s.State.Charm(curl) 552 c.Assert(err, gc.IsNil) 553 } 554 555 func (s *serviceSuite) TestAddCharmConcurrently(c *gc.C) { 556 var putBarrier sync.WaitGroup 557 var blobs blobs 558 s.PatchValue(service.NewStateStorage, func(uuid string, session *mgo.Session) statestorage.Storage { 559 storage := statestorage.NewStorage(uuid, session) 560 return &recordingStorage{Storage: storage, blobs: &blobs, putBarrier: &putBarrier} 561 }) 562 563 client := s.APIState.Client() 564 curl, _ := s.UploadCharm(c, "trusty/wordpress-3", "wordpress") 565 566 // Try adding the same charm concurrently from multiple goroutines 567 // to test no "duplicate key errors" are reported (see lp bug 568 // #1067979) and also at the end only one charm document is 569 // created. 570 571 var wg sync.WaitGroup 572 // We don't add them 1-by-1 because that would allow each goroutine to 573 // finish separately without actually synchronizing between them 574 putBarrier.Add(10) 575 for i := 0; i < 10; i++ { 576 wg.Add(1) 577 go func(index int) { 578 defer wg.Done() 579 580 c.Assert(client.AddCharm(curl, csparams.StableChannel), gc.IsNil, gc.Commentf("goroutine %d", index)) 581 sch, err := s.State.Charm(curl) 582 c.Assert(err, gc.IsNil, gc.Commentf("goroutine %d", index)) 583 c.Assert(sch.URL(), jc.DeepEquals, curl, gc.Commentf("goroutine %d", index)) 584 }(i) 585 } 586 wg.Wait() 587 588 blobs.Lock() 589 590 c.Assert(blobs.m, gc.HasLen, 10) 591 592 // Verify there is only a single uploaded charm remains and it 593 // contains the correct data. 594 sch, err := s.State.Charm(curl) 595 c.Assert(err, jc.ErrorIsNil) 596 storagePath := sch.StoragePath() 597 c.Assert(blobs.m[storagePath], jc.IsTrue) 598 for path, exists := range blobs.m { 599 if path != storagePath { 600 c.Assert(exists, jc.IsFalse) 601 } 602 } 603 604 storage := statestorage.NewStorage(s.State.ModelUUID(), s.State.MongoSession()) 605 s.assertUploaded(c, storage, sch.StoragePath(), sch.BundleSha256()) 606 } 607 608 func (s *serviceSuite) assertUploaded(c *gc.C, storage statestorage.Storage, storagePath, expectedSHA256 string) { 609 reader, _, err := storage.Get(storagePath) 610 c.Assert(err, jc.ErrorIsNil) 611 defer reader.Close() 612 downloadedSHA256, _, err := utils.ReadSHA256(reader) 613 c.Assert(err, jc.ErrorIsNil) 614 c.Assert(downloadedSHA256, gc.Equals, expectedSHA256) 615 } 616 617 func (s *serviceSuite) TestAddCharmOverwritesPlaceholders(c *gc.C) { 618 client := s.APIState.Client() 619 curl, _ := s.UploadCharm(c, "trusty/wordpress-42", "wordpress") 620 621 // Add a placeholder with the same charm URL. 622 err := s.State.AddStoreCharmPlaceholder(curl) 623 c.Assert(err, jc.ErrorIsNil) 624 _, err = s.State.Charm(curl) 625 c.Assert(err, jc.Satisfies, errors.IsNotFound) 626 627 // Now try to add the charm, which will convert the placeholder to 628 // a pending charm. 629 err = client.AddCharm(curl, csparams.StableChannel) 630 c.Assert(err, jc.ErrorIsNil) 631 632 // Make sure the document's flags were reset as expected. 633 sch, err := s.State.Charm(curl) 634 c.Assert(err, jc.ErrorIsNil) 635 c.Assert(sch.URL(), jc.DeepEquals, curl) 636 c.Assert(sch.IsPlaceholder(), jc.IsFalse) 637 c.Assert(sch.IsUploaded(), jc.IsTrue) 638 } 639 640 func (s *serviceSuite) TestServiceGetCharmURL(c *gc.C) { 641 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 642 result, err := s.serviceApi.GetCharmURL(params.ServiceGet{"wordpress"}) 643 c.Assert(err, jc.ErrorIsNil) 644 c.Assert(result.Error, gc.IsNil) 645 c.Assert(result.Result, gc.Equals, "local:quantal/wordpress-3") 646 } 647 648 func (s *serviceSuite) TestServiceSetCharm(c *gc.C) { 649 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 650 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 651 URL: curl.String(), 652 }) 653 c.Assert(err, jc.ErrorIsNil) 654 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 655 Services: []params.ServiceDeploy{{ 656 CharmUrl: curl.String(), 657 ServiceName: "service", 658 NumUnits: 3, 659 }}}) 660 c.Assert(err, jc.ErrorIsNil) 661 c.Assert(results.Results, gc.HasLen, 1) 662 c.Assert(results.Results[0].Error, gc.IsNil) 663 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 664 err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 665 URL: curl.String(), 666 }) 667 c.Assert(err, jc.ErrorIsNil) 668 err = s.serviceApi.SetCharm(params.ServiceSetCharm{ 669 ServiceName: "service", 670 CharmUrl: curl.String(), 671 }) 672 c.Assert(err, jc.ErrorIsNil) 673 674 // Ensure that the charm is not marked as forced. 675 service, err := s.State.Service("service") 676 c.Assert(err, jc.ErrorIsNil) 677 charm, force, err := service.Charm() 678 c.Assert(err, jc.ErrorIsNil) 679 c.Assert(charm.URL().String(), gc.Equals, curl.String()) 680 c.Assert(force, jc.IsFalse) 681 } 682 683 func (s *serviceSuite) setupServiceSetCharm(c *gc.C) { 684 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 685 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 686 URL: curl.String(), 687 }) 688 c.Assert(err, jc.ErrorIsNil) 689 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 690 Services: []params.ServiceDeploy{{ 691 CharmUrl: curl.String(), 692 ServiceName: "service", 693 NumUnits: 3, 694 }}}) 695 c.Assert(err, jc.ErrorIsNil) 696 c.Assert(results.Results, gc.HasLen, 1) 697 c.Assert(results.Results[0].Error, gc.IsNil) 698 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 699 err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 700 URL: curl.String(), 701 }) 702 c.Assert(err, jc.ErrorIsNil) 703 } 704 705 func (s *serviceSuite) assertServiceSetCharm(c *gc.C, forceUnits bool) { 706 err := s.serviceApi.SetCharm(params.ServiceSetCharm{ 707 ServiceName: "service", 708 CharmUrl: "cs:~who/precise/wordpress-3", 709 ForceUnits: forceUnits, 710 }) 711 c.Assert(err, jc.ErrorIsNil) 712 // Ensure that the charm is not marked as forced. 713 service, err := s.State.Service("service") 714 c.Assert(err, jc.ErrorIsNil) 715 charm, _, err := service.Charm() 716 c.Assert(err, jc.ErrorIsNil) 717 c.Assert(charm.URL().String(), gc.Equals, "cs:~who/precise/wordpress-3") 718 } 719 720 func (s *serviceSuite) assertServiceSetCharmBlocked(c *gc.C, msg string) { 721 err := s.serviceApi.SetCharm(params.ServiceSetCharm{ 722 ServiceName: "service", 723 CharmUrl: "cs:~who/precise/wordpress-3", 724 }) 725 s.AssertBlocked(c, err, msg) 726 } 727 728 func (s *serviceSuite) TestBlockDestroyServiceSetCharm(c *gc.C) { 729 s.setupServiceSetCharm(c) 730 s.BlockDestroyModel(c, "TestBlockDestroyServiceSetCharm") 731 s.assertServiceSetCharm(c, false) 732 } 733 734 func (s *serviceSuite) TestBlockRemoveServiceSetCharm(c *gc.C) { 735 s.setupServiceSetCharm(c) 736 s.BlockRemoveObject(c, "TestBlockRemoveServiceSetCharm") 737 s.assertServiceSetCharm(c, false) 738 } 739 740 func (s *serviceSuite) TestBlockChangesServiceSetCharm(c *gc.C) { 741 s.setupServiceSetCharm(c) 742 s.BlockAllChanges(c, "TestBlockChangesServiceSetCharm") 743 s.assertServiceSetCharmBlocked(c, "TestBlockChangesServiceSetCharm") 744 } 745 746 func (s *serviceSuite) TestServiceSetCharmForceUnits(c *gc.C) { 747 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 748 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 749 URL: curl.String(), 750 }) 751 c.Assert(err, jc.ErrorIsNil) 752 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 753 Services: []params.ServiceDeploy{{ 754 CharmUrl: curl.String(), 755 ServiceName: "service", 756 NumUnits: 3, 757 }}}) 758 c.Assert(err, jc.ErrorIsNil) 759 c.Assert(results.Results, gc.HasLen, 1) 760 c.Assert(results.Results[0].Error, gc.IsNil) 761 curl, _ = s.UploadCharm(c, "precise/wordpress-3", "wordpress") 762 err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 763 URL: curl.String(), 764 }) 765 c.Assert(err, jc.ErrorIsNil) 766 err = s.serviceApi.SetCharm(params.ServiceSetCharm{ 767 ServiceName: "service", 768 CharmUrl: curl.String(), 769 ForceUnits: true, 770 }) 771 c.Assert(err, jc.ErrorIsNil) 772 773 // Ensure that the charm is marked as forced. 774 service, err := s.State.Service("service") 775 c.Assert(err, jc.ErrorIsNil) 776 charm, force, err := service.Charm() 777 c.Assert(err, jc.ErrorIsNil) 778 c.Assert(charm.URL().String(), gc.Equals, curl.String()) 779 c.Assert(force, jc.IsTrue) 780 } 781 782 func (s *serviceSuite) TestBlockServiceSetCharmForce(c *gc.C) { 783 s.setupServiceSetCharm(c) 784 785 // block all changes 786 s.BlockAllChanges(c, "TestBlockServiceSetCharmForce") 787 s.BlockRemoveObject(c, "TestBlockServiceSetCharmForce") 788 s.BlockDestroyModel(c, "TestBlockServiceSetCharmForce") 789 790 s.assertServiceSetCharm(c, true) 791 } 792 793 func (s *serviceSuite) TestServiceSetCharmInvalidService(c *gc.C) { 794 err := s.serviceApi.SetCharm(params.ServiceSetCharm{ 795 ServiceName: "badservice", 796 CharmUrl: "cs:precise/wordpress-3", 797 ForceSeries: true, 798 ForceUnits: true, 799 }) 800 c.Assert(err, gc.ErrorMatches, `service "badservice" not found`) 801 } 802 803 func (s *serviceSuite) TestServiceAddCharmErrors(c *gc.C) { 804 for url, expect := range map[string]string{ 805 "wordpress": "charm URL must include revision", 806 "cs:wordpress": "charm URL must include revision", 807 "cs:precise/wordpress": "charm URL must include revision", 808 "cs:precise/wordpress-999999": `cannot retrieve "cs:precise/wordpress-999999": charm not found`, 809 } { 810 c.Logf("test %s", url) 811 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 812 URL: url, 813 }) 814 c.Check(err, gc.ErrorMatches, expect) 815 } 816 } 817 818 func (s *serviceSuite) TestServiceSetCharmLegacy(c *gc.C) { 819 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 820 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 821 URL: curl.String(), 822 }) 823 c.Assert(err, jc.ErrorIsNil) 824 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 825 Services: []params.ServiceDeploy{{ 826 CharmUrl: curl.String(), 827 ServiceName: "service", 828 }}}) 829 c.Assert(err, jc.ErrorIsNil) 830 c.Assert(results.Results, gc.HasLen, 1) 831 c.Assert(results.Results[0].Error, gc.IsNil) 832 curl, _ = s.UploadCharm(c, "trusty/dummy-1", "dummy") 833 err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 834 URL: curl.String(), 835 }) 836 c.Assert(err, jc.ErrorIsNil) 837 838 // Even with forceSeries = true, we can't change a charm where 839 // the series is sepcified in the URL. 840 err = s.serviceApi.SetCharm(params.ServiceSetCharm{ 841 ServiceName: "service", 842 CharmUrl: curl.String(), 843 ForceSeries: true, 844 }) 845 c.Assert(err, gc.ErrorMatches, "cannot change a service's series") 846 } 847 848 func (s *serviceSuite) TestServiceSetCharmUnsupportedSeries(c *gc.C) { 849 curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") 850 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 851 URL: curl.String(), 852 }) 853 c.Assert(err, jc.ErrorIsNil) 854 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 855 Services: []params.ServiceDeploy{{ 856 CharmUrl: curl.String(), 857 ServiceName: "service", 858 Series: "precise", 859 }}}) 860 c.Assert(err, jc.ErrorIsNil) 861 c.Assert(results.Results, gc.HasLen, 1) 862 c.Assert(results.Results[0].Error, gc.IsNil) 863 curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series2") 864 err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 865 URL: curl.String(), 866 }) 867 c.Assert(err, jc.ErrorIsNil) 868 869 err = s.serviceApi.SetCharm(params.ServiceSetCharm{ 870 ServiceName: "service", 871 CharmUrl: curl.String(), 872 }) 873 c.Assert(err, gc.ErrorMatches, "cannot upgrade charm, only these series are supported: trusty, wily") 874 } 875 876 func (s *serviceSuite) assertServiceSetCharmSeries(c *gc.C, upgradeCharm, series string) { 877 curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") 878 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 879 URL: curl.String(), 880 }) 881 c.Assert(err, jc.ErrorIsNil) 882 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 883 Services: []params.ServiceDeploy{{ 884 CharmUrl: curl.String(), 885 ServiceName: "service", 886 Series: "precise", 887 }}}) 888 c.Assert(err, jc.ErrorIsNil) 889 c.Assert(results.Results, gc.HasLen, 1) 890 c.Assert(results.Results[0].Error, gc.IsNil) 891 892 url := upgradeCharm 893 if series != "" { 894 url = series + "/" + upgradeCharm 895 } 896 curl, _ = s.UploadCharmMultiSeries(c, "~who/"+url, upgradeCharm) 897 err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 898 URL: curl.String(), 899 }) 900 c.Assert(err, jc.ErrorIsNil) 901 902 err = s.serviceApi.SetCharm(params.ServiceSetCharm{ 903 ServiceName: "service", 904 CharmUrl: curl.String(), 905 ForceSeries: true, 906 }) 907 c.Assert(err, jc.ErrorIsNil) 908 svc, err := s.State.Service("service") 909 c.Assert(err, jc.ErrorIsNil) 910 ch, _, err := svc.Charm() 911 c.Assert(err, jc.ErrorIsNil) 912 c.Assert(ch.URL().String(), gc.Equals, "cs:~who/"+url+"-0") 913 } 914 915 func (s *serviceSuite) TestServiceSetCharmUnsupportedSeriesForce(c *gc.C) { 916 s.assertServiceSetCharmSeries(c, "multi-series2", "") 917 } 918 919 func (s *serviceSuite) TestServiceSetCharmNoExplicitSupportedSeries(c *gc.C) { 920 s.assertServiceSetCharmSeries(c, "dummy", "precise") 921 } 922 923 func (s *serviceSuite) TestServiceSetCharmWrongOS(c *gc.C) { 924 curl, _ := s.UploadCharmMultiSeries(c, "~who/multi-series", "multi-series") 925 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 926 URL: curl.String(), 927 }) 928 c.Assert(err, jc.ErrorIsNil) 929 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 930 Services: []params.ServiceDeploy{{ 931 CharmUrl: curl.String(), 932 ServiceName: "service", 933 Series: "precise", 934 }}}) 935 c.Assert(err, jc.ErrorIsNil) 936 c.Assert(results.Results, gc.HasLen, 1) 937 c.Assert(results.Results[0].Error, gc.IsNil) 938 curl, _ = s.UploadCharmMultiSeries(c, "~who/multi-series-windows", "multi-series-windows") 939 err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 940 URL: curl.String(), 941 }) 942 c.Assert(err, jc.ErrorIsNil) 943 944 err = s.serviceApi.SetCharm(params.ServiceSetCharm{ 945 ServiceName: "service", 946 CharmUrl: curl.String(), 947 ForceSeries: true, 948 }) 949 c.Assert(err, gc.ErrorMatches, `cannot upgrade charm, OS "Ubuntu" not supported by charm`) 950 } 951 952 type testModeCharmRepo struct { 953 *charmrepo.CharmStore 954 testMode bool 955 } 956 957 // WithTestMode returns a repository Interface where test mode is enabled. 958 func (s *testModeCharmRepo) WithTestMode() charmrepo.Interface { 959 s.testMode = true 960 return s.CharmStore.WithTestMode() 961 } 962 963 func (s *serviceSuite) TestSpecializeStoreOnDeployServiceSetCharmAndAddCharm(c *gc.C) { 964 repo := &testModeCharmRepo{} 965 s.PatchValue(&csclient.ServerURL, s.Srv.URL) 966 newCharmStoreRepo := service.NewCharmStoreRepo 967 s.PatchValue(&service.NewCharmStoreRepo, func(c *csclient.Client) charmrepo.Interface { 968 repo.CharmStore = newCharmStoreRepo(c).(*charmrepo.CharmStore) 969 return repo 970 }) 971 attrs := map[string]interface{}{"test-mode": true} 972 err := s.State.UpdateModelConfig(attrs, nil, nil) 973 c.Assert(err, jc.ErrorIsNil) 974 975 // Check that the store's test mode is enabled when calling service Deploy. 976 curl, _ := s.UploadCharm(c, "trusty/dummy-1", "dummy") 977 err = service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 978 URL: curl.String(), 979 }) 980 c.Assert(err, jc.ErrorIsNil) 981 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 982 Services: []params.ServiceDeploy{{ 983 CharmUrl: curl.String(), 984 ServiceName: "service", 985 NumUnits: 3, 986 }}}) 987 c.Assert(err, jc.ErrorIsNil) 988 c.Assert(results.Results, gc.HasLen, 1) 989 c.Assert(results.Results[0].Error, gc.IsNil) 990 c.Assert(repo.testMode, jc.IsTrue) 991 992 // Check that the store's test mode is enabled when calling SetCharm. 993 curl, _ = s.UploadCharm(c, "trusty/wordpress-2", "wordpress") 994 err = s.serviceApi.SetCharm(params.ServiceSetCharm{ 995 ServiceName: "service", 996 CharmUrl: curl.String(), 997 }) 998 c.Assert(repo.testMode, jc.IsTrue) 999 1000 // Check that the store's test mode is enabled when calling AddCharm. 1001 curl, _ = s.UploadCharm(c, "utopic/riak-42", "riak") 1002 err = s.APIState.Client().AddCharm(curl, csparams.StableChannel) 1003 c.Assert(err, jc.ErrorIsNil) 1004 c.Assert(repo.testMode, jc.IsTrue) 1005 } 1006 1007 func (s *serviceSuite) setupServiceDeploy(c *gc.C, args string) (*charm.URL, charm.Charm, constraints.Value) { 1008 curl, ch := s.UploadCharm(c, "precise/dummy-42", "dummy") 1009 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1010 URL: curl.String(), 1011 }) 1012 c.Assert(err, jc.ErrorIsNil) 1013 cons := constraints.MustParse(args) 1014 return curl, ch, cons 1015 } 1016 1017 func (s *serviceSuite) assertServiceDeployPrincipal(c *gc.C, curl *charm.URL, ch charm.Charm, mem4g constraints.Value) { 1018 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 1019 Services: []params.ServiceDeploy{{ 1020 CharmUrl: curl.String(), 1021 ServiceName: "service", 1022 NumUnits: 3, 1023 Constraints: mem4g, 1024 }}}) 1025 c.Assert(err, jc.ErrorIsNil) 1026 c.Assert(results.Results, gc.HasLen, 1) 1027 c.Assert(results.Results[0].Error, gc.IsNil) 1028 apiservertesting.AssertPrincipalServiceDeployed(c, s.State, "service", curl, false, ch, mem4g) 1029 } 1030 1031 func (s *serviceSuite) assertServiceDeployPrincipalBlocked(c *gc.C, msg string, curl *charm.URL, mem4g constraints.Value) { 1032 _, err := s.serviceApi.Deploy(params.ServicesDeploy{ 1033 Services: []params.ServiceDeploy{{ 1034 CharmUrl: curl.String(), 1035 ServiceName: "service", 1036 NumUnits: 3, 1037 Constraints: mem4g, 1038 }}}) 1039 s.AssertBlocked(c, err, msg) 1040 } 1041 1042 func (s *serviceSuite) TestBlockDestroyServiceDeployPrincipal(c *gc.C) { 1043 curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G") 1044 s.BlockDestroyModel(c, "TestBlockDestroyServiceDeployPrincipal") 1045 s.assertServiceDeployPrincipal(c, curl, bundle, cons) 1046 } 1047 1048 func (s *serviceSuite) TestBlockRemoveServiceDeployPrincipal(c *gc.C) { 1049 curl, bundle, cons := s.setupServiceDeploy(c, "mem=4G") 1050 s.BlockRemoveObject(c, "TestBlockRemoveServiceDeployPrincipal") 1051 s.assertServiceDeployPrincipal(c, curl, bundle, cons) 1052 } 1053 1054 func (s *serviceSuite) TestBlockChangesServiceDeployPrincipal(c *gc.C) { 1055 curl, _, cons := s.setupServiceDeploy(c, "mem=4G") 1056 s.BlockAllChanges(c, "TestBlockChangesServiceDeployPrincipal") 1057 s.assertServiceDeployPrincipalBlocked(c, "TestBlockChangesServiceDeployPrincipal", curl, cons) 1058 } 1059 1060 func (s *serviceSuite) TestServiceDeploySubordinate(c *gc.C) { 1061 curl, ch := s.UploadCharm(c, "utopic/logging-47", "logging") 1062 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1063 URL: curl.String(), 1064 }) 1065 c.Assert(err, jc.ErrorIsNil) 1066 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 1067 Services: []params.ServiceDeploy{{ 1068 CharmUrl: curl.String(), 1069 ServiceName: "service-name", 1070 }}}) 1071 c.Assert(err, jc.ErrorIsNil) 1072 c.Assert(results.Results, gc.HasLen, 1) 1073 c.Assert(results.Results[0].Error, gc.IsNil) 1074 1075 service, err := s.State.Service("service-name") 1076 c.Assert(err, jc.ErrorIsNil) 1077 charm, force, err := service.Charm() 1078 c.Assert(err, jc.ErrorIsNil) 1079 c.Assert(force, jc.IsFalse) 1080 c.Assert(charm.URL(), gc.DeepEquals, curl) 1081 c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta()) 1082 c.Assert(charm.Config(), gc.DeepEquals, ch.Config()) 1083 1084 units, err := service.AllUnits() 1085 c.Assert(err, jc.ErrorIsNil) 1086 c.Assert(units, gc.HasLen, 0) 1087 } 1088 1089 func (s *serviceSuite) TestServiceDeployConfig(c *gc.C) { 1090 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 1091 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1092 URL: curl.String(), 1093 }) 1094 c.Assert(err, jc.ErrorIsNil) 1095 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 1096 Services: []params.ServiceDeploy{{ 1097 CharmUrl: curl.String(), 1098 ServiceName: "service-name", 1099 NumUnits: 1, 1100 ConfigYAML: "service-name:\n username: fred", 1101 }}}) 1102 c.Assert(err, jc.ErrorIsNil) 1103 c.Assert(results.Results, gc.HasLen, 1) 1104 c.Assert(results.Results[0].Error, gc.IsNil) 1105 1106 service, err := s.State.Service("service-name") 1107 c.Assert(err, jc.ErrorIsNil) 1108 settings, err := service.ConfigSettings() 1109 c.Assert(err, jc.ErrorIsNil) 1110 c.Assert(settings, gc.DeepEquals, charm.Settings{"username": "fred"}) 1111 } 1112 1113 func (s *serviceSuite) TestServiceDeployConfigError(c *gc.C) { 1114 // TODO(fwereade): test Config/ConfigYAML handling directly on srvClient. 1115 // Can't be done cleanly until it's extracted similarly to Machiner. 1116 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 1117 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1118 URL: curl.String(), 1119 }) 1120 c.Assert(err, jc.ErrorIsNil) 1121 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 1122 Services: []params.ServiceDeploy{{ 1123 CharmUrl: curl.String(), 1124 ServiceName: "service-name", 1125 NumUnits: 1, 1126 ConfigYAML: "service-name:\n skill-level: fred", 1127 }}}) 1128 c.Assert(err, jc.ErrorIsNil) 1129 c.Assert(results.Results, gc.HasLen, 1) 1130 c.Assert(results.Results[0].Error, gc.ErrorMatches, `option "skill-level" expected int, got "fred"`) 1131 _, err = s.State.Service("service-name") 1132 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1133 } 1134 1135 func (s *serviceSuite) TestServiceDeployToMachine(c *gc.C) { 1136 curl, ch := s.UploadCharm(c, "precise/dummy-0", "dummy") 1137 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1138 URL: curl.String(), 1139 }) 1140 c.Assert(err, jc.ErrorIsNil) 1141 1142 machine, err := s.State.AddMachine("precise", state.JobHostUnits) 1143 c.Assert(err, jc.ErrorIsNil) 1144 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 1145 Services: []params.ServiceDeploy{{ 1146 CharmUrl: curl.String(), 1147 ServiceName: "service-name", 1148 NumUnits: 1, 1149 ConfigYAML: "service-name:\n username: fred", 1150 }}}) 1151 c.Assert(err, jc.ErrorIsNil) 1152 c.Assert(results.Results, gc.HasLen, 1) 1153 c.Assert(results.Results[0].Error, gc.IsNil) 1154 1155 service, err := s.State.Service("service-name") 1156 c.Assert(err, jc.ErrorIsNil) 1157 charm, force, err := service.Charm() 1158 c.Assert(err, jc.ErrorIsNil) 1159 c.Assert(force, jc.IsFalse) 1160 c.Assert(charm.URL(), gc.DeepEquals, curl) 1161 c.Assert(charm.Meta(), gc.DeepEquals, ch.Meta()) 1162 c.Assert(charm.Config(), gc.DeepEquals, ch.Config()) 1163 1164 errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("service-name/0")}) 1165 c.Assert(errs, gc.DeepEquals, []error{nil}) 1166 c.Assert(err, jc.ErrorIsNil) 1167 1168 units, err := service.AllUnits() 1169 c.Assert(err, jc.ErrorIsNil) 1170 c.Assert(units, gc.HasLen, 1) 1171 1172 mid, err := units[0].AssignedMachineId() 1173 c.Assert(err, jc.ErrorIsNil) 1174 c.Assert(mid, gc.Equals, machine.Id()) 1175 } 1176 1177 func (s *serviceSuite) TestServiceDeployToMachineNotFound(c *gc.C) { 1178 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 1179 Services: []params.ServiceDeploy{{ 1180 CharmUrl: "cs:precise/service-name-1", 1181 ServiceName: "service-name", 1182 NumUnits: 1, 1183 Placement: []*instance.Placement{instance.MustParsePlacement("42")}, 1184 }}}) 1185 c.Assert(err, jc.ErrorIsNil) 1186 c.Assert(results.Results, gc.HasLen, 1) 1187 c.Assert(results.Results[0].Error, gc.ErrorMatches, `cannot deploy "service-name" to machine 42: machine 42 not found`) 1188 1189 _, err = s.State.Service("service-name") 1190 c.Assert(err, gc.ErrorMatches, `service "service-name" not found`) 1191 } 1192 1193 func (s *serviceSuite) TestServiceDeployServiceOwner(c *gc.C) { 1194 curl, _ := s.UploadCharm(c, "precise/dummy-0", "dummy") 1195 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1196 URL: curl.String(), 1197 }) 1198 c.Assert(err, jc.ErrorIsNil) 1199 1200 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 1201 Services: []params.ServiceDeploy{{ 1202 CharmUrl: curl.String(), 1203 ServiceName: "service", 1204 NumUnits: 3, 1205 }}}) 1206 c.Assert(err, jc.ErrorIsNil) 1207 c.Assert(results.Results, gc.HasLen, 1) 1208 c.Assert(results.Results[0].Error, gc.IsNil) 1209 1210 service, err := s.State.Service("service") 1211 c.Assert(err, jc.ErrorIsNil) 1212 c.Assert(service.GetOwnerTag(), gc.Equals, s.authorizer.GetAuthTag().String()) 1213 } 1214 1215 func (s *serviceSuite) deployServiceForUpdateTests(c *gc.C) { 1216 curl, _ := s.UploadCharm(c, "precise/dummy-1", "dummy") 1217 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1218 URL: curl.String(), 1219 }) 1220 c.Assert(err, jc.ErrorIsNil) 1221 results, err := s.serviceApi.Deploy(params.ServicesDeploy{ 1222 Services: []params.ServiceDeploy{{ 1223 CharmUrl: curl.String(), 1224 ServiceName: "service", 1225 NumUnits: 1, 1226 }}}) 1227 c.Assert(err, jc.ErrorIsNil) 1228 c.Assert(results.Results, gc.HasLen, 1) 1229 c.Assert(results.Results[0].Error, gc.IsNil) 1230 } 1231 1232 func (s *serviceSuite) checkClientServiceUpdateSetCharm(c *gc.C, forceCharmUrl bool) { 1233 s.deployServiceForUpdateTests(c) 1234 curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") 1235 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1236 URL: curl.String(), 1237 }) 1238 c.Assert(err, jc.ErrorIsNil) 1239 1240 // Update the charm for the service. 1241 args := params.ServiceUpdate{ 1242 ServiceName: "service", 1243 CharmUrl: curl.String(), 1244 ForceCharmUrl: forceCharmUrl, 1245 } 1246 err = s.serviceApi.Update(args) 1247 c.Assert(err, jc.ErrorIsNil) 1248 1249 // Ensure the charm has been updated and and the force flag correctly set. 1250 service, err := s.State.Service("service") 1251 c.Assert(err, jc.ErrorIsNil) 1252 ch, force, err := service.Charm() 1253 c.Assert(err, jc.ErrorIsNil) 1254 c.Assert(ch.URL().String(), gc.Equals, curl.String()) 1255 c.Assert(force, gc.Equals, forceCharmUrl) 1256 } 1257 1258 func (s *serviceSuite) TestServiceUpdateSetCharm(c *gc.C) { 1259 s.checkClientServiceUpdateSetCharm(c, false) 1260 } 1261 1262 func (s *serviceSuite) TestBlockDestroyServiceUpdate(c *gc.C) { 1263 s.BlockDestroyModel(c, "TestBlockDestroyServiceUpdate") 1264 s.checkClientServiceUpdateSetCharm(c, false) 1265 } 1266 1267 func (s *serviceSuite) TestBlockRemoveServiceUpdate(c *gc.C) { 1268 s.BlockRemoveObject(c, "TestBlockRemoveServiceUpdate") 1269 s.checkClientServiceUpdateSetCharm(c, false) 1270 } 1271 1272 func (s *serviceSuite) setupServiceUpdate(c *gc.C) string { 1273 s.deployServiceForUpdateTests(c) 1274 curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") 1275 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1276 URL: curl.String(), 1277 }) 1278 c.Assert(err, jc.ErrorIsNil) 1279 return curl.String() 1280 } 1281 1282 func (s *serviceSuite) TestBlockChangeServiceUpdate(c *gc.C) { 1283 curl := s.setupServiceUpdate(c) 1284 s.BlockAllChanges(c, "TestBlockChangeServiceUpdate") 1285 // Update the charm for the service. 1286 args := params.ServiceUpdate{ 1287 ServiceName: "service", 1288 CharmUrl: curl, 1289 ForceCharmUrl: false, 1290 } 1291 err := s.serviceApi.Update(args) 1292 s.AssertBlocked(c, err, "TestBlockChangeServiceUpdate") 1293 } 1294 1295 func (s *serviceSuite) TestServiceUpdateForceSetCharm(c *gc.C) { 1296 s.checkClientServiceUpdateSetCharm(c, true) 1297 } 1298 1299 func (s *serviceSuite) TestBlockServiceUpdateForced(c *gc.C) { 1300 curl := s.setupServiceUpdate(c) 1301 1302 // block all changes. Force should ignore block :) 1303 s.BlockAllChanges(c, "TestBlockServiceUpdateForced") 1304 s.BlockDestroyModel(c, "TestBlockServiceUpdateForced") 1305 s.BlockRemoveObject(c, "TestBlockServiceUpdateForced") 1306 1307 // Update the charm for the service. 1308 args := params.ServiceUpdate{ 1309 ServiceName: "service", 1310 CharmUrl: curl, 1311 ForceCharmUrl: true, 1312 } 1313 err := s.serviceApi.Update(args) 1314 c.Assert(err, jc.ErrorIsNil) 1315 1316 // Ensure the charm has been updated and and the force flag correctly set. 1317 service, err := s.State.Service("service") 1318 c.Assert(err, jc.ErrorIsNil) 1319 ch, force, err := service.Charm() 1320 c.Assert(err, jc.ErrorIsNil) 1321 c.Assert(ch.URL().String(), gc.Equals, curl) 1322 c.Assert(force, jc.IsTrue) 1323 } 1324 1325 func (s *serviceSuite) TestServiceUpdateSetCharmNotFound(c *gc.C) { 1326 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1327 args := params.ServiceUpdate{ 1328 ServiceName: "wordpress", 1329 CharmUrl: "cs:precise/wordpress-999999", 1330 } 1331 err := s.serviceApi.Update(args) 1332 c.Check(err, gc.ErrorMatches, `charm "cs:precise/wordpress-999999" not found`) 1333 } 1334 1335 func (s *serviceSuite) TestServiceUpdateSetMinUnits(c *gc.C) { 1336 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1337 1338 // Set minimum units for the service. 1339 minUnits := 2 1340 args := params.ServiceUpdate{ 1341 ServiceName: "dummy", 1342 MinUnits: &minUnits, 1343 } 1344 err := s.serviceApi.Update(args) 1345 c.Assert(err, jc.ErrorIsNil) 1346 1347 // Ensure the minimum number of units has been set. 1348 c.Assert(service.Refresh(), gc.IsNil) 1349 c.Assert(service.MinUnits(), gc.Equals, minUnits) 1350 } 1351 1352 func (s *serviceSuite) TestServiceUpdateSetMinUnitsError(c *gc.C) { 1353 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1354 1355 // Set a negative minimum number of units for the service. 1356 minUnits := -1 1357 args := params.ServiceUpdate{ 1358 ServiceName: "dummy", 1359 MinUnits: &minUnits, 1360 } 1361 err := s.serviceApi.Update(args) 1362 c.Assert(err, gc.ErrorMatches, 1363 `cannot set minimum units for service "dummy": cannot set a negative minimum number of units`) 1364 1365 // Ensure the minimum number of units has not been set. 1366 c.Assert(service.Refresh(), gc.IsNil) 1367 c.Assert(service.MinUnits(), gc.Equals, 0) 1368 } 1369 1370 func (s *serviceSuite) TestServiceUpdateSetSettingsStrings(c *gc.C) { 1371 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1372 1373 // Update settings for the service. 1374 args := params.ServiceUpdate{ 1375 ServiceName: "dummy", 1376 SettingsStrings: map[string]string{"title": "s-title", "username": "s-user"}, 1377 } 1378 err := s.serviceApi.Update(args) 1379 c.Assert(err, jc.ErrorIsNil) 1380 1381 // Ensure the settings have been correctly updated. 1382 expected := charm.Settings{"title": "s-title", "username": "s-user"} 1383 obtained, err := service.ConfigSettings() 1384 c.Assert(err, jc.ErrorIsNil) 1385 c.Assert(obtained, gc.DeepEquals, expected) 1386 } 1387 1388 func (s *serviceSuite) TestServiceUpdateSetSettingsYAML(c *gc.C) { 1389 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1390 1391 // Update settings for the service. 1392 args := params.ServiceUpdate{ 1393 ServiceName: "dummy", 1394 SettingsYAML: "dummy:\n title: y-title\n username: y-user", 1395 } 1396 err := s.serviceApi.Update(args) 1397 c.Assert(err, jc.ErrorIsNil) 1398 1399 // Ensure the settings have been correctly updated. 1400 expected := charm.Settings{"title": "y-title", "username": "y-user"} 1401 obtained, err := service.ConfigSettings() 1402 c.Assert(err, jc.ErrorIsNil) 1403 c.Assert(obtained, gc.DeepEquals, expected) 1404 } 1405 1406 func (s *serviceSuite) TestClientServiceUpdateSetSettingsGetYAML(c *gc.C) { 1407 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1408 1409 // Update settings for the service. 1410 args := params.ServiceUpdate{ 1411 ServiceName: "dummy", 1412 SettingsYAML: "charm: dummy\nservice: dummy\nsettings:\n title:\n value: y-title\n type: string\n username:\n value: y-user\n ignore:\n blah: true", 1413 } 1414 err := s.serviceApi.Update(args) 1415 c.Assert(err, jc.ErrorIsNil) 1416 1417 // Ensure the settings have been correctly updated. 1418 expected := charm.Settings{"title": "y-title", "username": "y-user"} 1419 obtained, err := service.ConfigSettings() 1420 c.Assert(err, jc.ErrorIsNil) 1421 c.Assert(obtained, gc.DeepEquals, expected) 1422 } 1423 1424 func (s *serviceSuite) TestServiceUpdateSetConstraints(c *gc.C) { 1425 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1426 1427 // Update constraints for the service. 1428 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 1429 c.Assert(err, jc.ErrorIsNil) 1430 args := params.ServiceUpdate{ 1431 ServiceName: "dummy", 1432 Constraints: &cons, 1433 } 1434 err = s.serviceApi.Update(args) 1435 c.Assert(err, jc.ErrorIsNil) 1436 1437 // Ensure the constraints have been correctly updated. 1438 obtained, err := service.Constraints() 1439 c.Assert(err, jc.ErrorIsNil) 1440 c.Assert(obtained, gc.DeepEquals, cons) 1441 } 1442 1443 func (s *serviceSuite) TestServiceUpdateAllParams(c *gc.C) { 1444 s.deployServiceForUpdateTests(c) 1445 curl, _ := s.UploadCharm(c, "precise/wordpress-3", "wordpress") 1446 err := service.AddCharmWithAuthorization(s.State, params.AddCharmWithAuthorization{ 1447 URL: curl.String(), 1448 }) 1449 c.Assert(err, jc.ErrorIsNil) 1450 1451 // Update all the service attributes. 1452 minUnits := 3 1453 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 1454 c.Assert(err, jc.ErrorIsNil) 1455 args := params.ServiceUpdate{ 1456 ServiceName: "service", 1457 CharmUrl: curl.String(), 1458 ForceCharmUrl: true, 1459 MinUnits: &minUnits, 1460 SettingsStrings: map[string]string{"blog-title": "string-title"}, 1461 SettingsYAML: "service:\n blog-title: yaml-title\n", 1462 Constraints: &cons, 1463 } 1464 err = s.serviceApi.Update(args) 1465 c.Assert(err, jc.ErrorIsNil) 1466 1467 // Ensure the service has been correctly updated. 1468 service, err := s.State.Service("service") 1469 c.Assert(err, jc.ErrorIsNil) 1470 1471 // Check the charm. 1472 ch, force, err := service.Charm() 1473 c.Assert(err, jc.ErrorIsNil) 1474 c.Assert(ch.URL().String(), gc.Equals, curl.String()) 1475 c.Assert(force, jc.IsTrue) 1476 1477 // Check the minimum number of units. 1478 c.Assert(service.MinUnits(), gc.Equals, minUnits) 1479 1480 // Check the settings: also ensure the YAML settings take precedence 1481 // over strings ones. 1482 expectedSettings := charm.Settings{"blog-title": "yaml-title"} 1483 obtainedSettings, err := service.ConfigSettings() 1484 c.Assert(err, jc.ErrorIsNil) 1485 c.Assert(obtainedSettings, gc.DeepEquals, expectedSettings) 1486 1487 // Check the constraints. 1488 obtainedConstraints, err := service.Constraints() 1489 c.Assert(err, jc.ErrorIsNil) 1490 c.Assert(obtainedConstraints, gc.DeepEquals, cons) 1491 } 1492 1493 func (s *serviceSuite) TestServiceUpdateNoParams(c *gc.C) { 1494 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1495 1496 // Calling Update with no parameters set is a no-op. 1497 args := params.ServiceUpdate{ServiceName: "wordpress"} 1498 err := s.serviceApi.Update(args) 1499 c.Assert(err, jc.ErrorIsNil) 1500 } 1501 1502 func (s *serviceSuite) TestServiceUpdateNoService(c *gc.C) { 1503 err := s.serviceApi.Update(params.ServiceUpdate{}) 1504 c.Assert(err, gc.ErrorMatches, `"" is not a valid service name`) 1505 } 1506 1507 func (s *serviceSuite) TestServiceUpdateInvalidService(c *gc.C) { 1508 args := params.ServiceUpdate{ServiceName: "no-such-service"} 1509 err := s.serviceApi.Update(args) 1510 c.Assert(err, gc.ErrorMatches, `service "no-such-service" not found`) 1511 } 1512 1513 var ( 1514 validSetTestValue = "a value with spaces\nand newline\nand UTF-8 characters: \U0001F604 / \U0001F44D" 1515 ) 1516 1517 func (s *serviceSuite) TestServiceSet(c *gc.C) { 1518 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1519 1520 err := s.serviceApi.Set(params.ServiceSet{ServiceName: "dummy", Options: map[string]string{ 1521 "title": "foobar", 1522 "username": validSetTestValue, 1523 }}) 1524 c.Assert(err, jc.ErrorIsNil) 1525 settings, err := dummy.ConfigSettings() 1526 c.Assert(err, jc.ErrorIsNil) 1527 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1528 "title": "foobar", 1529 "username": validSetTestValue, 1530 }) 1531 1532 err = s.serviceApi.Set(params.ServiceSet{ServiceName: "dummy", Options: map[string]string{ 1533 "title": "barfoo", 1534 "username": "", 1535 }}) 1536 c.Assert(err, jc.ErrorIsNil) 1537 settings, err = dummy.ConfigSettings() 1538 c.Assert(err, jc.ErrorIsNil) 1539 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1540 "title": "barfoo", 1541 "username": "", 1542 }) 1543 } 1544 1545 func (s *serviceSuite) assertServiceSetBlocked(c *gc.C, dummy *state.Service, msg string) { 1546 err := s.serviceApi.Set(params.ServiceSet{ 1547 ServiceName: "dummy", 1548 Options: map[string]string{ 1549 "title": "foobar", 1550 "username": validSetTestValue}}) 1551 s.AssertBlocked(c, err, msg) 1552 } 1553 1554 func (s *serviceSuite) assertServiceSet(c *gc.C, dummy *state.Service) { 1555 err := s.serviceApi.Set(params.ServiceSet{ 1556 ServiceName: "dummy", 1557 Options: map[string]string{ 1558 "title": "foobar", 1559 "username": validSetTestValue}}) 1560 c.Assert(err, jc.ErrorIsNil) 1561 settings, err := dummy.ConfigSettings() 1562 c.Assert(err, jc.ErrorIsNil) 1563 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1564 "title": "foobar", 1565 "username": validSetTestValue, 1566 }) 1567 } 1568 1569 func (s *serviceSuite) TestBlockDestroyServiceSet(c *gc.C) { 1570 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1571 s.BlockDestroyModel(c, "TestBlockDestroyServiceSet") 1572 s.assertServiceSet(c, dummy) 1573 } 1574 1575 func (s *serviceSuite) TestBlockRemoveServiceSet(c *gc.C) { 1576 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1577 s.BlockRemoveObject(c, "TestBlockRemoveServiceSet") 1578 s.assertServiceSet(c, dummy) 1579 } 1580 1581 func (s *serviceSuite) TestBlockChangesServiceSet(c *gc.C) { 1582 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1583 s.BlockAllChanges(c, "TestBlockChangesServiceSet") 1584 s.assertServiceSetBlocked(c, dummy, "TestBlockChangesServiceSet") 1585 } 1586 1587 func (s *serviceSuite) TestServerUnset(c *gc.C) { 1588 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1589 1590 err := s.serviceApi.Set(params.ServiceSet{ServiceName: "dummy", Options: map[string]string{ 1591 "title": "foobar", 1592 "username": "user name", 1593 }}) 1594 c.Assert(err, jc.ErrorIsNil) 1595 settings, err := dummy.ConfigSettings() 1596 c.Assert(err, jc.ErrorIsNil) 1597 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1598 "title": "foobar", 1599 "username": "user name", 1600 }) 1601 1602 err = s.serviceApi.Unset(params.ServiceUnset{ServiceName: "dummy", Options: []string{"username"}}) 1603 c.Assert(err, jc.ErrorIsNil) 1604 settings, err = dummy.ConfigSettings() 1605 c.Assert(err, jc.ErrorIsNil) 1606 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1607 "title": "foobar", 1608 }) 1609 } 1610 1611 func (s *serviceSuite) setupServerUnsetBlocked(c *gc.C) *state.Service { 1612 dummy := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1613 1614 err := s.serviceApi.Set(params.ServiceSet{ 1615 ServiceName: "dummy", 1616 Options: map[string]string{ 1617 "title": "foobar", 1618 "username": "user name", 1619 }}) 1620 c.Assert(err, jc.ErrorIsNil) 1621 settings, err := dummy.ConfigSettings() 1622 c.Assert(err, jc.ErrorIsNil) 1623 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1624 "title": "foobar", 1625 "username": "user name", 1626 }) 1627 return dummy 1628 } 1629 1630 func (s *serviceSuite) assertServerUnset(c *gc.C, dummy *state.Service) { 1631 err := s.serviceApi.Unset(params.ServiceUnset{ 1632 ServiceName: "dummy", 1633 Options: []string{"username"}, 1634 }) 1635 c.Assert(err, jc.ErrorIsNil) 1636 settings, err := dummy.ConfigSettings() 1637 c.Assert(err, jc.ErrorIsNil) 1638 c.Assert(settings, gc.DeepEquals, charm.Settings{ 1639 "title": "foobar", 1640 }) 1641 } 1642 1643 func (s *serviceSuite) assertServerUnsetBlocked(c *gc.C, dummy *state.Service, msg string) { 1644 err := s.serviceApi.Unset(params.ServiceUnset{ 1645 ServiceName: "dummy", 1646 Options: []string{"username"}, 1647 }) 1648 s.AssertBlocked(c, err, msg) 1649 } 1650 1651 func (s *serviceSuite) TestBlockDestroyServerUnset(c *gc.C) { 1652 dummy := s.setupServerUnsetBlocked(c) 1653 s.BlockDestroyModel(c, "TestBlockDestroyServerUnset") 1654 s.assertServerUnset(c, dummy) 1655 } 1656 1657 func (s *serviceSuite) TestBlockRemoveServerUnset(c *gc.C) { 1658 dummy := s.setupServerUnsetBlocked(c) 1659 s.BlockRemoveObject(c, "TestBlockRemoveServerUnset") 1660 s.assertServerUnset(c, dummy) 1661 } 1662 1663 func (s *serviceSuite) TestBlockChangesServerUnset(c *gc.C) { 1664 dummy := s.setupServerUnsetBlocked(c) 1665 s.BlockAllChanges(c, "TestBlockChangesServerUnset") 1666 s.assertServerUnsetBlocked(c, dummy, "TestBlockChangesServerUnset") 1667 } 1668 1669 var clientAddServiceUnitsTests = []struct { 1670 about string 1671 service string // if not set, defaults to 'dummy' 1672 expected []string 1673 to string 1674 err string 1675 }{ 1676 { 1677 about: "returns unit names", 1678 expected: []string{"dummy/0", "dummy/1", "dummy/2"}, 1679 }, 1680 { 1681 about: "fails trying to add zero units", 1682 err: "must add at least one unit", 1683 }, 1684 { 1685 // Note: chained-state, we add 1 unit here, but the 3 units 1686 // from the first condition still exist 1687 about: "force the unit onto bootstrap machine", 1688 expected: []string{"dummy/3"}, 1689 to: "0", 1690 }, 1691 { 1692 about: "unknown service name", 1693 service: "unknown-service", 1694 err: `service "unknown-service" not found`, 1695 }, 1696 } 1697 1698 func (s *serviceSuite) TestClientAddServiceUnits(c *gc.C) { 1699 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1700 for i, t := range clientAddServiceUnitsTests { 1701 c.Logf("test %d. %s", i, t.about) 1702 serviceName := t.service 1703 if serviceName == "" { 1704 serviceName = "dummy" 1705 } 1706 args := params.AddServiceUnits{ 1707 ServiceName: serviceName, 1708 NumUnits: len(t.expected), 1709 } 1710 if t.to != "" { 1711 args.Placement = []*instance.Placement{instance.MustParsePlacement(t.to)} 1712 } 1713 result, err := s.serviceApi.AddUnits(args) 1714 if t.err != "" { 1715 c.Assert(err, gc.ErrorMatches, t.err) 1716 continue 1717 } 1718 c.Assert(err, jc.ErrorIsNil) 1719 c.Assert(result.Units, gc.DeepEquals, t.expected) 1720 } 1721 // Test that we actually assigned the unit to machine 0 1722 forcedUnit, err := s.BackingState.Unit("dummy/3") 1723 c.Assert(err, jc.ErrorIsNil) 1724 assignedMachine, err := forcedUnit.AssignedMachineId() 1725 c.Assert(err, jc.ErrorIsNil) 1726 c.Assert(assignedMachine, gc.Equals, "0") 1727 } 1728 1729 func (s *serviceSuite) TestAddServiceUnitsToNewContainer(c *gc.C) { 1730 svc := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1731 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 1732 c.Assert(err, jc.ErrorIsNil) 1733 1734 _, err = s.serviceApi.AddUnits(params.AddServiceUnits{ 1735 ServiceName: "dummy", 1736 NumUnits: 1, 1737 Placement: []*instance.Placement{instance.MustParsePlacement("lxc:" + machine.Id())}, 1738 }) 1739 c.Assert(err, jc.ErrorIsNil) 1740 1741 units, err := svc.AllUnits() 1742 c.Assert(err, jc.ErrorIsNil) 1743 mid, err := units[0].AssignedMachineId() 1744 c.Assert(err, jc.ErrorIsNil) 1745 c.Assert(mid, gc.Equals, machine.Id()+"/lxc/0") 1746 } 1747 1748 var addServiceUnitTests = []struct { 1749 about string 1750 service string // if not set, defaults to 'dummy' 1751 expected []string 1752 machineIds []string 1753 placement []*instance.Placement 1754 err string 1755 }{ 1756 { 1757 about: "valid placement directives", 1758 expected: []string{"dummy/0"}, 1759 placement: []*instance.Placement{{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "valid"}}, 1760 machineIds: []string{"1"}, 1761 }, { 1762 about: "direct machine assignment placement directive", 1763 expected: []string{"dummy/1", "dummy/2"}, 1764 placement: []*instance.Placement{{"#", "1"}, {"lxc", "1"}}, 1765 machineIds: []string{"1", "1/lxc/0"}, 1766 }, { 1767 about: "invalid placement directive", 1768 err: ".* invalid placement is invalid", 1769 expected: []string{"dummy/3"}, 1770 placement: []*instance.Placement{{"deadbeef-0bad-400d-8000-4b1d0d06f00d", "invalid"}}, 1771 }, 1772 } 1773 1774 func (s *serviceSuite) TestAddServiceUnits(c *gc.C) { 1775 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1776 // Add a machine for the units to be placed on. 1777 _, err := s.State.AddMachine("quantal", state.JobHostUnits) 1778 c.Assert(err, jc.ErrorIsNil) 1779 for i, t := range addServiceUnitTests { 1780 c.Logf("test %d. %s", i, t.about) 1781 serviceName := t.service 1782 if serviceName == "" { 1783 serviceName = "dummy" 1784 } 1785 result, err := s.serviceApi.AddUnits(params.AddServiceUnits{ 1786 ServiceName: serviceName, 1787 NumUnits: len(t.expected), 1788 Placement: t.placement, 1789 }) 1790 if t.err != "" { 1791 c.Assert(err, gc.ErrorMatches, t.err) 1792 continue 1793 } 1794 c.Assert(err, jc.ErrorIsNil) 1795 c.Assert(result.Units, gc.DeepEquals, t.expected) 1796 for i, unitName := range result.Units { 1797 u, err := s.BackingState.Unit(unitName) 1798 c.Assert(err, jc.ErrorIsNil) 1799 assignedMachine, err := u.AssignedMachineId() 1800 c.Assert(err, jc.ErrorIsNil) 1801 c.Assert(assignedMachine, gc.Equals, t.machineIds[i]) 1802 } 1803 } 1804 } 1805 1806 func (s *serviceSuite) assertAddServiceUnits(c *gc.C) { 1807 result, err := s.serviceApi.AddUnits(params.AddServiceUnits{ 1808 ServiceName: "dummy", 1809 NumUnits: 3, 1810 }) 1811 c.Assert(err, jc.ErrorIsNil) 1812 c.Assert(result.Units, gc.DeepEquals, []string{"dummy/0", "dummy/1", "dummy/2"}) 1813 1814 // Test that we actually assigned the unit to machine 0 1815 forcedUnit, err := s.BackingState.Unit("dummy/0") 1816 c.Assert(err, jc.ErrorIsNil) 1817 assignedMachine, err := forcedUnit.AssignedMachineId() 1818 c.Assert(err, jc.ErrorIsNil) 1819 c.Assert(assignedMachine, gc.Equals, "0") 1820 } 1821 1822 func (s *serviceSuite) TestServiceCharmRelations(c *gc.C) { 1823 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1824 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 1825 eps, err := s.State.InferEndpoints("logging", "wordpress") 1826 c.Assert(err, jc.ErrorIsNil) 1827 _, err = s.State.AddRelation(eps...) 1828 c.Assert(err, jc.ErrorIsNil) 1829 1830 _, err = s.serviceApi.CharmRelations(params.ServiceCharmRelations{"blah"}) 1831 c.Assert(err, gc.ErrorMatches, `service "blah" not found`) 1832 1833 result, err := s.serviceApi.CharmRelations(params.ServiceCharmRelations{"wordpress"}) 1834 c.Assert(err, jc.ErrorIsNil) 1835 c.Assert(result.CharmRelations, gc.DeepEquals, []string{ 1836 "cache", "db", "juju-info", "logging-dir", "monitoring-port", "url", 1837 }) 1838 } 1839 1840 func (s *serviceSuite) assertAddServiceUnitsBlocked(c *gc.C, msg string) { 1841 _, err := s.serviceApi.AddUnits(params.AddServiceUnits{ 1842 ServiceName: "dummy", 1843 NumUnits: 3, 1844 }) 1845 s.AssertBlocked(c, err, msg) 1846 } 1847 1848 func (s *serviceSuite) TestBlockDestroyAddServiceUnits(c *gc.C) { 1849 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1850 s.BlockDestroyModel(c, "TestBlockDestroyAddServiceUnits") 1851 s.assertAddServiceUnits(c) 1852 } 1853 1854 func (s *serviceSuite) TestBlockRemoveAddServiceUnits(c *gc.C) { 1855 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1856 s.BlockRemoveObject(c, "TestBlockRemoveAddServiceUnits") 1857 s.assertAddServiceUnits(c) 1858 } 1859 1860 func (s *serviceSuite) TestBlockChangeAddServiceUnits(c *gc.C) { 1861 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1862 s.BlockAllChanges(c, "TestBlockChangeAddServiceUnits") 1863 s.assertAddServiceUnitsBlocked(c, "TestBlockChangeAddServiceUnits") 1864 } 1865 1866 func (s *serviceSuite) TestAddUnitToMachineNotFound(c *gc.C) { 1867 s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 1868 _, err := s.serviceApi.AddUnits(params.AddServiceUnits{ 1869 ServiceName: "dummy", 1870 NumUnits: 3, 1871 Placement: []*instance.Placement{instance.MustParsePlacement("42")}, 1872 }) 1873 c.Assert(err, gc.ErrorMatches, `adding new machine to host unit "dummy/0": machine 42 not found`) 1874 } 1875 1876 func (s *serviceSuite) TestServiceExpose(c *gc.C) { 1877 charm := s.AddTestingCharm(c, "dummy") 1878 serviceNames := []string{"dummy-service", "exposed-service"} 1879 svcs := make([]*state.Service, len(serviceNames)) 1880 var err error 1881 for i, name := range serviceNames { 1882 svcs[i] = s.AddTestingService(c, name, charm) 1883 c.Assert(svcs[i].IsExposed(), jc.IsFalse) 1884 } 1885 err = svcs[1].SetExposed() 1886 c.Assert(err, jc.ErrorIsNil) 1887 c.Assert(svcs[1].IsExposed(), jc.IsTrue) 1888 for i, t := range serviceExposeTests { 1889 c.Logf("test %d. %s", i, t.about) 1890 err = s.serviceApi.Expose(params.ServiceExpose{t.service}) 1891 if t.err != "" { 1892 c.Assert(err, gc.ErrorMatches, t.err) 1893 } else { 1894 c.Assert(err, jc.ErrorIsNil) 1895 service, err := s.State.Service(t.service) 1896 c.Assert(err, jc.ErrorIsNil) 1897 c.Assert(service.IsExposed(), gc.Equals, t.exposed) 1898 } 1899 } 1900 } 1901 1902 func (s *serviceSuite) setupServiceExpose(c *gc.C) { 1903 charm := s.AddTestingCharm(c, "dummy") 1904 serviceNames := []string{"dummy-service", "exposed-service"} 1905 svcs := make([]*state.Service, len(serviceNames)) 1906 var err error 1907 for i, name := range serviceNames { 1908 svcs[i] = s.AddTestingService(c, name, charm) 1909 c.Assert(svcs[i].IsExposed(), jc.IsFalse) 1910 } 1911 err = svcs[1].SetExposed() 1912 c.Assert(err, jc.ErrorIsNil) 1913 c.Assert(svcs[1].IsExposed(), jc.IsTrue) 1914 } 1915 1916 var serviceExposeTests = []struct { 1917 about string 1918 service string 1919 err string 1920 exposed bool 1921 }{ 1922 { 1923 about: "unknown service name", 1924 service: "unknown-service", 1925 err: `service "unknown-service" not found`, 1926 }, 1927 { 1928 about: "expose a service", 1929 service: "dummy-service", 1930 exposed: true, 1931 }, 1932 { 1933 about: "expose an already exposed service", 1934 service: "exposed-service", 1935 exposed: true, 1936 }, 1937 } 1938 1939 func (s *serviceSuite) assertServiceExpose(c *gc.C) { 1940 for i, t := range serviceExposeTests { 1941 c.Logf("test %d. %s", i, t.about) 1942 err := s.serviceApi.Expose(params.ServiceExpose{t.service}) 1943 if t.err != "" { 1944 c.Assert(err, gc.ErrorMatches, t.err) 1945 } else { 1946 c.Assert(err, jc.ErrorIsNil) 1947 service, err := s.State.Service(t.service) 1948 c.Assert(err, jc.ErrorIsNil) 1949 c.Assert(service.IsExposed(), gc.Equals, t.exposed) 1950 } 1951 } 1952 } 1953 1954 func (s *serviceSuite) assertServiceExposeBlocked(c *gc.C, msg string) { 1955 for i, t := range serviceExposeTests { 1956 c.Logf("test %d. %s", i, t.about) 1957 err := s.serviceApi.Expose(params.ServiceExpose{t.service}) 1958 s.AssertBlocked(c, err, msg) 1959 } 1960 } 1961 1962 func (s *serviceSuite) TestBlockDestroyServiceExpose(c *gc.C) { 1963 s.setupServiceExpose(c) 1964 s.BlockDestroyModel(c, "TestBlockDestroyServiceExpose") 1965 s.assertServiceExpose(c) 1966 } 1967 1968 func (s *serviceSuite) TestBlockRemoveServiceExpose(c *gc.C) { 1969 s.setupServiceExpose(c) 1970 s.BlockRemoveObject(c, "TestBlockRemoveServiceExpose") 1971 s.assertServiceExpose(c) 1972 } 1973 1974 func (s *serviceSuite) TestBlockChangesServiceExpose(c *gc.C) { 1975 s.setupServiceExpose(c) 1976 s.BlockAllChanges(c, "TestBlockChangesServiceExpose") 1977 s.assertServiceExposeBlocked(c, "TestBlockChangesServiceExpose") 1978 } 1979 1980 var serviceUnexposeTests = []struct { 1981 about string 1982 service string 1983 err string 1984 initial bool 1985 expected bool 1986 }{ 1987 { 1988 about: "unknown service name", 1989 service: "unknown-service", 1990 err: `service "unknown-service" not found`, 1991 }, 1992 { 1993 about: "unexpose a service", 1994 service: "dummy-service", 1995 initial: true, 1996 expected: false, 1997 }, 1998 { 1999 about: "unexpose an already unexposed service", 2000 service: "dummy-service", 2001 initial: false, 2002 expected: false, 2003 }, 2004 } 2005 2006 func (s *serviceSuite) TestServiceUnexpose(c *gc.C) { 2007 charm := s.AddTestingCharm(c, "dummy") 2008 for i, t := range serviceUnexposeTests { 2009 c.Logf("test %d. %s", i, t.about) 2010 svc := s.AddTestingService(c, "dummy-service", charm) 2011 if t.initial { 2012 svc.SetExposed() 2013 } 2014 c.Assert(svc.IsExposed(), gc.Equals, t.initial) 2015 err := s.serviceApi.Unexpose(params.ServiceUnexpose{t.service}) 2016 if t.err == "" { 2017 c.Assert(err, jc.ErrorIsNil) 2018 svc.Refresh() 2019 c.Assert(svc.IsExposed(), gc.Equals, t.expected) 2020 } else { 2021 c.Assert(err, gc.ErrorMatches, t.err) 2022 } 2023 err = svc.Destroy() 2024 c.Assert(err, jc.ErrorIsNil) 2025 } 2026 } 2027 2028 func (s *serviceSuite) setupServiceUnexpose(c *gc.C) *state.Service { 2029 charm := s.AddTestingCharm(c, "dummy") 2030 svc := s.AddTestingService(c, "dummy-service", charm) 2031 svc.SetExposed() 2032 c.Assert(svc.IsExposed(), gc.Equals, true) 2033 return svc 2034 } 2035 2036 func (s *serviceSuite) assertServiceUnexpose(c *gc.C, svc *state.Service) { 2037 err := s.serviceApi.Unexpose(params.ServiceUnexpose{"dummy-service"}) 2038 c.Assert(err, jc.ErrorIsNil) 2039 svc.Refresh() 2040 c.Assert(svc.IsExposed(), gc.Equals, false) 2041 err = svc.Destroy() 2042 c.Assert(err, jc.ErrorIsNil) 2043 } 2044 2045 func (s *serviceSuite) assertServiceUnexposeBlocked(c *gc.C, svc *state.Service, msg string) { 2046 err := s.serviceApi.Unexpose(params.ServiceUnexpose{"dummy-service"}) 2047 s.AssertBlocked(c, err, msg) 2048 err = svc.Destroy() 2049 c.Assert(err, jc.ErrorIsNil) 2050 } 2051 2052 func (s *serviceSuite) TestBlockDestroyServiceUnexpose(c *gc.C) { 2053 svc := s.setupServiceUnexpose(c) 2054 s.BlockDestroyModel(c, "TestBlockDestroyServiceUnexpose") 2055 s.assertServiceUnexpose(c, svc) 2056 } 2057 2058 func (s *serviceSuite) TestBlockRemoveServiceUnexpose(c *gc.C) { 2059 svc := s.setupServiceUnexpose(c) 2060 s.BlockRemoveObject(c, "TestBlockRemoveServiceUnexpose") 2061 s.assertServiceUnexpose(c, svc) 2062 } 2063 2064 func (s *serviceSuite) TestBlockChangesServiceUnexpose(c *gc.C) { 2065 svc := s.setupServiceUnexpose(c) 2066 s.BlockAllChanges(c, "TestBlockChangesServiceUnexpose") 2067 s.assertServiceUnexposeBlocked(c, svc, "TestBlockChangesServiceUnexpose") 2068 } 2069 2070 var serviceDestroyTests = []struct { 2071 about string 2072 service string 2073 err string 2074 }{ 2075 { 2076 about: "unknown service name", 2077 service: "unknown-service", 2078 err: `service "unknown-service" not found`, 2079 }, 2080 { 2081 about: "destroy a service", 2082 service: "dummy-service", 2083 }, 2084 { 2085 about: "destroy an already destroyed service", 2086 service: "dummy-service", 2087 err: `service "dummy-service" not found`, 2088 }, 2089 } 2090 2091 func (s *serviceSuite) TestServiceDestroy(c *gc.C) { 2092 s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy")) 2093 for i, t := range serviceDestroyTests { 2094 c.Logf("test %d. %s", i, t.about) 2095 err := s.serviceApi.Destroy(params.ServiceDestroy{t.service}) 2096 if t.err != "" { 2097 c.Assert(err, gc.ErrorMatches, t.err) 2098 } else { 2099 c.Assert(err, jc.ErrorIsNil) 2100 } 2101 } 2102 2103 // Now do Destroy on a service with units. Destroy will 2104 // cause the service to be not-Alive, but will not remove its 2105 // document. 2106 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2107 serviceName := "wordpress" 2108 service, err := s.State.Service(serviceName) 2109 c.Assert(err, jc.ErrorIsNil) 2110 err = s.serviceApi.Destroy(params.ServiceDestroy{serviceName}) 2111 c.Assert(err, jc.ErrorIsNil) 2112 err = service.Refresh() 2113 c.Assert(err, jc.Satisfies, errors.IsNotFound) 2114 } 2115 2116 func assertLife(c *gc.C, entity state.Living, life state.Life) { 2117 err := entity.Refresh() 2118 c.Assert(err, jc.ErrorIsNil) 2119 c.Assert(entity.Life(), gc.Equals, life) 2120 } 2121 2122 func (s *serviceSuite) TestBlockServiceDestroy(c *gc.C) { 2123 s.AddTestingService(c, "dummy-service", s.AddTestingCharm(c, "dummy")) 2124 2125 // block remove-objects 2126 s.BlockRemoveObject(c, "TestBlockServiceDestroy") 2127 err := s.serviceApi.Destroy(params.ServiceDestroy{"dummy-service"}) 2128 s.AssertBlocked(c, err, "TestBlockServiceDestroy") 2129 // Tests may have invalid service names. 2130 service, err := s.State.Service("dummy-service") 2131 if err == nil { 2132 // For valid service names, check that service is alive :-) 2133 assertLife(c, service, state.Alive) 2134 } 2135 } 2136 2137 func (s *serviceSuite) TestDestroyPrincipalUnits(c *gc.C) { 2138 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2139 units := make([]*state.Unit, 5) 2140 for i := range units { 2141 unit, err := wordpress.AddUnit() 2142 c.Assert(err, jc.ErrorIsNil) 2143 err = unit.SetAgentStatus(status.StatusIdle, "", nil) 2144 c.Assert(err, jc.ErrorIsNil) 2145 units[i] = unit 2146 } 2147 s.assertDestroyPrincipalUnits(c, units) 2148 } 2149 2150 func (s *serviceSuite) TestDestroySubordinateUnits(c *gc.C) { 2151 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2152 wordpress0, err := wordpress.AddUnit() 2153 c.Assert(err, jc.ErrorIsNil) 2154 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2155 eps, err := s.State.InferEndpoints("logging", "wordpress") 2156 c.Assert(err, jc.ErrorIsNil) 2157 rel, err := s.State.AddRelation(eps...) 2158 c.Assert(err, jc.ErrorIsNil) 2159 ru, err := rel.Unit(wordpress0) 2160 c.Assert(err, jc.ErrorIsNil) 2161 err = ru.EnterScope(nil) 2162 c.Assert(err, jc.ErrorIsNil) 2163 logging0, err := s.State.Unit("logging/0") 2164 c.Assert(err, jc.ErrorIsNil) 2165 2166 // Try to destroy the subordinate alone; check it fails. 2167 err = s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2168 UnitNames: []string{"logging/0"}, 2169 }) 2170 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) 2171 assertLife(c, logging0, state.Alive) 2172 2173 s.assertDestroySubordinateUnits(c, wordpress0, logging0) 2174 } 2175 2176 func (s *serviceSuite) assertDestroyPrincipalUnits(c *gc.C, units []*state.Unit) { 2177 // Destroy 2 of them; check they become Dying. 2178 err := s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2179 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2180 }) 2181 c.Assert(err, jc.ErrorIsNil) 2182 assertLife(c, units[0], state.Dying) 2183 assertLife(c, units[1], state.Dying) 2184 2185 // Try to destroy an Alive one and a Dying one; check 2186 // it destroys the Alive one and ignores the Dying one. 2187 err = s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2188 UnitNames: []string{"wordpress/2", "wordpress/0"}, 2189 }) 2190 c.Assert(err, jc.ErrorIsNil) 2191 assertLife(c, units[2], state.Dying) 2192 2193 // Try to destroy an Alive one along with a nonexistent one; check that 2194 // the valid instruction is followed but the invalid one is warned about. 2195 err = s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2196 UnitNames: []string{"boojum/123", "wordpress/3"}, 2197 }) 2198 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`) 2199 assertLife(c, units[3], state.Dying) 2200 2201 // Make one Dead, and destroy an Alive one alongside it; check no errors. 2202 wp0, err := s.State.Unit("wordpress/0") 2203 c.Assert(err, jc.ErrorIsNil) 2204 err = wp0.EnsureDead() 2205 c.Assert(err, jc.ErrorIsNil) 2206 err = s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2207 UnitNames: []string{"wordpress/0", "wordpress/4"}, 2208 }) 2209 c.Assert(err, jc.ErrorIsNil) 2210 assertLife(c, units[0], state.Dead) 2211 assertLife(c, units[4], state.Dying) 2212 } 2213 2214 func (s *serviceSuite) setupDestroyPrincipalUnits(c *gc.C) []*state.Unit { 2215 units := make([]*state.Unit, 5) 2216 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2217 for i := range units { 2218 unit, err := wordpress.AddUnit() 2219 c.Assert(err, jc.ErrorIsNil) 2220 err = unit.SetAgentStatus(status.StatusIdle, "", nil) 2221 c.Assert(err, jc.ErrorIsNil) 2222 units[i] = unit 2223 } 2224 return units 2225 } 2226 2227 func (s *serviceSuite) assertBlockedErrorAndLiveliness( 2228 c *gc.C, 2229 err error, 2230 msg string, 2231 living1 state.Living, 2232 living2 state.Living, 2233 living3 state.Living, 2234 living4 state.Living, 2235 ) { 2236 s.AssertBlocked(c, err, msg) 2237 assertLife(c, living1, state.Alive) 2238 assertLife(c, living2, state.Alive) 2239 assertLife(c, living3, state.Alive) 2240 assertLife(c, living4, state.Alive) 2241 } 2242 2243 func (s *serviceSuite) TestBlockChangesDestroyPrincipalUnits(c *gc.C) { 2244 units := s.setupDestroyPrincipalUnits(c) 2245 s.BlockAllChanges(c, "TestBlockChangesDestroyPrincipalUnits") 2246 err := s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2247 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2248 }) 2249 s.assertBlockedErrorAndLiveliness(c, err, "TestBlockChangesDestroyPrincipalUnits", units[0], units[1], units[2], units[3]) 2250 } 2251 2252 func (s *serviceSuite) TestBlockRemoveDestroyPrincipalUnits(c *gc.C) { 2253 units := s.setupDestroyPrincipalUnits(c) 2254 s.BlockRemoveObject(c, "TestBlockRemoveDestroyPrincipalUnits") 2255 err := s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2256 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2257 }) 2258 s.assertBlockedErrorAndLiveliness(c, err, "TestBlockRemoveDestroyPrincipalUnits", units[0], units[1], units[2], units[3]) 2259 } 2260 2261 func (s *serviceSuite) TestBlockDestroyDestroyPrincipalUnits(c *gc.C) { 2262 units := s.setupDestroyPrincipalUnits(c) 2263 s.BlockDestroyModel(c, "TestBlockDestroyDestroyPrincipalUnits") 2264 err := s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2265 UnitNames: []string{"wordpress/0", "wordpress/1"}, 2266 }) 2267 c.Assert(err, jc.ErrorIsNil) 2268 assertLife(c, units[0], state.Dying) 2269 assertLife(c, units[1], state.Dying) 2270 } 2271 2272 func (s *serviceSuite) assertDestroySubordinateUnits(c *gc.C, wordpress0, logging0 *state.Unit) { 2273 // Try to destroy the principal and the subordinate together; check it warns 2274 // about the subordinate, but destroys the one it can. (The principal unit 2275 // agent will be responsible for destroying the subordinate.) 2276 err := s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2277 UnitNames: []string{"wordpress/0", "logging/0"}, 2278 }) 2279 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`) 2280 assertLife(c, wordpress0, state.Dying) 2281 assertLife(c, logging0, state.Alive) 2282 } 2283 2284 func (s *serviceSuite) TestBlockRemoveDestroySubordinateUnits(c *gc.C) { 2285 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2286 wordpress0, err := wordpress.AddUnit() 2287 c.Assert(err, jc.ErrorIsNil) 2288 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2289 eps, err := s.State.InferEndpoints("logging", "wordpress") 2290 c.Assert(err, jc.ErrorIsNil) 2291 rel, err := s.State.AddRelation(eps...) 2292 c.Assert(err, jc.ErrorIsNil) 2293 ru, err := rel.Unit(wordpress0) 2294 c.Assert(err, jc.ErrorIsNil) 2295 err = ru.EnterScope(nil) 2296 c.Assert(err, jc.ErrorIsNil) 2297 logging0, err := s.State.Unit("logging/0") 2298 c.Assert(err, jc.ErrorIsNil) 2299 2300 s.BlockRemoveObject(c, "TestBlockRemoveDestroySubordinateUnits") 2301 // Try to destroy the subordinate alone; check it fails. 2302 err = s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2303 UnitNames: []string{"logging/0"}, 2304 }) 2305 s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits") 2306 assertLife(c, rel, state.Alive) 2307 assertLife(c, wordpress0, state.Alive) 2308 assertLife(c, logging0, state.Alive) 2309 2310 err = s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2311 UnitNames: []string{"wordpress/0", "logging/0"}, 2312 }) 2313 s.AssertBlocked(c, err, "TestBlockRemoveDestroySubordinateUnits") 2314 assertLife(c, wordpress0, state.Alive) 2315 assertLife(c, logging0, state.Alive) 2316 assertLife(c, rel, state.Alive) 2317 } 2318 2319 func (s *serviceSuite) TestBlockChangesDestroySubordinateUnits(c *gc.C) { 2320 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2321 wordpress0, err := wordpress.AddUnit() 2322 c.Assert(err, jc.ErrorIsNil) 2323 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2324 eps, err := s.State.InferEndpoints("logging", "wordpress") 2325 c.Assert(err, jc.ErrorIsNil) 2326 rel, err := s.State.AddRelation(eps...) 2327 c.Assert(err, jc.ErrorIsNil) 2328 ru, err := rel.Unit(wordpress0) 2329 c.Assert(err, jc.ErrorIsNil) 2330 err = ru.EnterScope(nil) 2331 c.Assert(err, jc.ErrorIsNil) 2332 logging0, err := s.State.Unit("logging/0") 2333 c.Assert(err, jc.ErrorIsNil) 2334 2335 s.BlockAllChanges(c, "TestBlockChangesDestroySubordinateUnits") 2336 // Try to destroy the subordinate alone; check it fails. 2337 err = s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2338 UnitNames: []string{"logging/0"}, 2339 }) 2340 s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits") 2341 assertLife(c, rel, state.Alive) 2342 assertLife(c, wordpress0, state.Alive) 2343 assertLife(c, logging0, state.Alive) 2344 2345 err = s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2346 UnitNames: []string{"wordpress/0", "logging/0"}, 2347 }) 2348 s.AssertBlocked(c, err, "TestBlockChangesDestroySubordinateUnits") 2349 assertLife(c, wordpress0, state.Alive) 2350 assertLife(c, logging0, state.Alive) 2351 assertLife(c, rel, state.Alive) 2352 } 2353 2354 func (s *serviceSuite) TestBlockDestroyDestroySubordinateUnits(c *gc.C) { 2355 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2356 wordpress0, err := wordpress.AddUnit() 2357 c.Assert(err, jc.ErrorIsNil) 2358 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2359 eps, err := s.State.InferEndpoints("logging", "wordpress") 2360 c.Assert(err, jc.ErrorIsNil) 2361 rel, err := s.State.AddRelation(eps...) 2362 c.Assert(err, jc.ErrorIsNil) 2363 ru, err := rel.Unit(wordpress0) 2364 c.Assert(err, jc.ErrorIsNil) 2365 err = ru.EnterScope(nil) 2366 c.Assert(err, jc.ErrorIsNil) 2367 logging0, err := s.State.Unit("logging/0") 2368 c.Assert(err, jc.ErrorIsNil) 2369 2370 s.BlockDestroyModel(c, "TestBlockDestroyDestroySubordinateUnits") 2371 // Try to destroy the subordinate alone; check it fails. 2372 err = s.serviceApi.DestroyUnits(params.DestroyServiceUnits{ 2373 UnitNames: []string{"logging/0"}, 2374 }) 2375 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) 2376 assertLife(c, logging0, state.Alive) 2377 2378 s.assertDestroySubordinateUnits(c, wordpress0, logging0) 2379 } 2380 2381 func (s *serviceSuite) TestClientSetServiceConstraints(c *gc.C) { 2382 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 2383 2384 // Update constraints for the service. 2385 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 2386 c.Assert(err, jc.ErrorIsNil) 2387 err = s.serviceApi.SetConstraints(params.SetConstraints{ServiceName: "dummy", Constraints: cons}) 2388 c.Assert(err, jc.ErrorIsNil) 2389 2390 // Ensure the constraints have been correctly updated. 2391 obtained, err := service.Constraints() 2392 c.Assert(err, jc.ErrorIsNil) 2393 c.Assert(obtained, gc.DeepEquals, cons) 2394 } 2395 2396 func (s *serviceSuite) setupSetServiceConstraints(c *gc.C) (*state.Service, constraints.Value) { 2397 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 2398 // Update constraints for the service. 2399 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 2400 c.Assert(err, jc.ErrorIsNil) 2401 return service, cons 2402 } 2403 2404 func (s *serviceSuite) assertSetServiceConstraints(c *gc.C, service *state.Service, cons constraints.Value) { 2405 err := s.serviceApi.SetConstraints(params.SetConstraints{ServiceName: "dummy", Constraints: cons}) 2406 c.Assert(err, jc.ErrorIsNil) 2407 // Ensure the constraints have been correctly updated. 2408 obtained, err := service.Constraints() 2409 c.Assert(err, jc.ErrorIsNil) 2410 c.Assert(obtained, gc.DeepEquals, cons) 2411 } 2412 2413 func (s *serviceSuite) assertSetServiceConstraintsBlocked(c *gc.C, msg string, service *state.Service, cons constraints.Value) { 2414 err := s.serviceApi.SetConstraints(params.SetConstraints{ServiceName: "dummy", Constraints: cons}) 2415 s.AssertBlocked(c, err, msg) 2416 } 2417 2418 func (s *serviceSuite) TestBlockDestroySetServiceConstraints(c *gc.C) { 2419 svc, cons := s.setupSetServiceConstraints(c) 2420 s.BlockDestroyModel(c, "TestBlockDestroySetServiceConstraints") 2421 s.assertSetServiceConstraints(c, svc, cons) 2422 } 2423 2424 func (s *serviceSuite) TestBlockRemoveSetServiceConstraints(c *gc.C) { 2425 svc, cons := s.setupSetServiceConstraints(c) 2426 s.BlockRemoveObject(c, "TestBlockRemoveSetServiceConstraints") 2427 s.assertSetServiceConstraints(c, svc, cons) 2428 } 2429 2430 func (s *serviceSuite) TestBlockChangesSetServiceConstraints(c *gc.C) { 2431 svc, cons := s.setupSetServiceConstraints(c) 2432 s.BlockAllChanges(c, "TestBlockChangesSetServiceConstraints") 2433 s.assertSetServiceConstraintsBlocked(c, "TestBlockChangesSetServiceConstraints", svc, cons) 2434 } 2435 2436 func (s *serviceSuite) TestClientGetServiceConstraints(c *gc.C) { 2437 service := s.AddTestingService(c, "dummy", s.AddTestingCharm(c, "dummy")) 2438 2439 // Set constraints for the service. 2440 cons, err := constraints.Parse("mem=4096", "cpu-cores=2") 2441 c.Assert(err, jc.ErrorIsNil) 2442 err = service.SetConstraints(cons) 2443 c.Assert(err, jc.ErrorIsNil) 2444 2445 // Check we can get the constraints. 2446 result, err := s.serviceApi.GetConstraints(params.GetServiceConstraints{"dummy"}) 2447 c.Assert(err, jc.ErrorIsNil) 2448 c.Assert(result.Constraints, gc.DeepEquals, cons) 2449 } 2450 2451 func (s *serviceSuite) checkEndpoints(c *gc.C, endpoints map[string]charm.Relation) { 2452 c.Assert(endpoints["wordpress"], gc.DeepEquals, charm.Relation{ 2453 Name: "db", 2454 Role: charm.RelationRole("requirer"), 2455 Interface: "mysql", 2456 Optional: false, 2457 Limit: 1, 2458 Scope: charm.RelationScope("global"), 2459 }) 2460 c.Assert(endpoints["mysql"], gc.DeepEquals, charm.Relation{ 2461 Name: "server", 2462 Role: charm.RelationRole("provider"), 2463 Interface: "mysql", 2464 Optional: false, 2465 Limit: 0, 2466 Scope: charm.RelationScope("global"), 2467 }) 2468 } 2469 2470 func (s *serviceSuite) setupRelationScenario(c *gc.C) { 2471 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2472 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2473 eps, err := s.State.InferEndpoints("logging", "wordpress") 2474 c.Assert(err, jc.ErrorIsNil) 2475 _, err = s.State.AddRelation(eps...) 2476 c.Assert(err, jc.ErrorIsNil) 2477 } 2478 2479 func (s *serviceSuite) assertAddRelation(c *gc.C, endpoints []string) { 2480 s.setupRelationScenario(c) 2481 res, err := s.serviceApi.AddRelation(params.AddRelation{Endpoints: endpoints}) 2482 c.Assert(err, jc.ErrorIsNil) 2483 s.checkEndpoints(c, res.Endpoints) 2484 // Show that the relation was added. 2485 wpSvc, err := s.State.Service("wordpress") 2486 c.Assert(err, jc.ErrorIsNil) 2487 rels, err := wpSvc.Relations() 2488 // There are 2 relations - the logging-wordpress one set up in the 2489 // scenario and the one created in this test. 2490 c.Assert(len(rels), gc.Equals, 2) 2491 mySvc, err := s.State.Service("mysql") 2492 c.Assert(err, jc.ErrorIsNil) 2493 rels, err = mySvc.Relations() 2494 c.Assert(err, jc.ErrorIsNil) 2495 c.Assert(len(rels), gc.Equals, 1) 2496 } 2497 2498 func (s *serviceSuite) TestSuccessfullyAddRelation(c *gc.C) { 2499 endpoints := []string{"wordpress", "mysql"} 2500 s.assertAddRelation(c, endpoints) 2501 } 2502 2503 func (s *serviceSuite) TestBlockDestroyAddRelation(c *gc.C) { 2504 s.BlockDestroyModel(c, "TestBlockDestroyAddRelation") 2505 s.assertAddRelation(c, []string{"wordpress", "mysql"}) 2506 } 2507 func (s *serviceSuite) TestBlockRemoveAddRelation(c *gc.C) { 2508 s.BlockRemoveObject(c, "TestBlockRemoveAddRelation") 2509 s.assertAddRelation(c, []string{"wordpress", "mysql"}) 2510 } 2511 2512 func (s *serviceSuite) TestBlockChangesAddRelation(c *gc.C) { 2513 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2514 s.BlockAllChanges(c, "TestBlockChangesAddRelation") 2515 _, err := s.serviceApi.AddRelation(params.AddRelation{Endpoints: []string{"wordpress", "mysql"}}) 2516 s.AssertBlocked(c, err, "TestBlockChangesAddRelation") 2517 } 2518 2519 func (s *serviceSuite) TestSuccessfullyAddRelationSwapped(c *gc.C) { 2520 // Show that the order of the services listed in the AddRelation call 2521 // does not matter. This is a repeat of the previous test with the service 2522 // names swapped. 2523 endpoints := []string{"mysql", "wordpress"} 2524 s.assertAddRelation(c, endpoints) 2525 } 2526 2527 func (s *serviceSuite) TestCallWithOnlyOneEndpoint(c *gc.C) { 2528 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2529 endpoints := []string{"wordpress"} 2530 _, err := s.serviceApi.AddRelation(params.AddRelation{Endpoints: endpoints}) 2531 c.Assert(err, gc.ErrorMatches, "no relations found") 2532 } 2533 2534 func (s *serviceSuite) TestCallWithOneEndpointTooMany(c *gc.C) { 2535 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2536 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 2537 endpoints := []string{"wordpress", "mysql", "logging"} 2538 _, err := s.serviceApi.AddRelation(params.AddRelation{Endpoints: endpoints}) 2539 c.Assert(err, gc.ErrorMatches, "cannot relate 3 endpoints") 2540 } 2541 2542 func (s *serviceSuite) TestAddAlreadyAddedRelation(c *gc.C) { 2543 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2544 // Add a relation between wordpress and mysql. 2545 endpoints := []string{"wordpress", "mysql"} 2546 eps, err := s.State.InferEndpoints(endpoints...) 2547 c.Assert(err, jc.ErrorIsNil) 2548 _, err = s.State.AddRelation(eps...) 2549 c.Assert(err, jc.ErrorIsNil) 2550 // And try to add it again. 2551 _, err = s.serviceApi.AddRelation(params.AddRelation{Endpoints: endpoints}) 2552 c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation already exists`) 2553 } 2554 2555 func (s *serviceSuite) setupDestroyRelationScenario(c *gc.C, endpoints []string) *state.Relation { 2556 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2557 // Add a relation between the endpoints. 2558 eps, err := s.State.InferEndpoints(endpoints...) 2559 c.Assert(err, jc.ErrorIsNil) 2560 relation, err := s.State.AddRelation(eps...) 2561 c.Assert(err, jc.ErrorIsNil) 2562 return relation 2563 } 2564 2565 func (s *serviceSuite) assertDestroyRelation(c *gc.C, endpoints []string) { 2566 s.assertDestroyRelationSuccess( 2567 c, 2568 s.setupDestroyRelationScenario(c, endpoints), 2569 endpoints) 2570 } 2571 2572 func (s *serviceSuite) assertDestroyRelationSuccess(c *gc.C, relation *state.Relation, endpoints []string) { 2573 err := s.serviceApi.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2574 c.Assert(err, jc.ErrorIsNil) 2575 // Show that the relation was removed. 2576 c.Assert(relation.Refresh(), jc.Satisfies, errors.IsNotFound) 2577 } 2578 2579 func (s *serviceSuite) TestSuccessfulDestroyRelation(c *gc.C) { 2580 endpoints := []string{"wordpress", "mysql"} 2581 s.assertDestroyRelation(c, endpoints) 2582 } 2583 2584 func (s *serviceSuite) TestSuccessfullyDestroyRelationSwapped(c *gc.C) { 2585 // Show that the order of the services listed in the DestroyRelation call 2586 // does not matter. This is a repeat of the previous test with the service 2587 // names swapped. 2588 endpoints := []string{"mysql", "wordpress"} 2589 s.assertDestroyRelation(c, endpoints) 2590 } 2591 2592 func (s *serviceSuite) TestNoRelation(c *gc.C) { 2593 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2594 endpoints := []string{"wordpress", "mysql"} 2595 err := s.serviceApi.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2596 c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`) 2597 } 2598 2599 func (s *serviceSuite) TestAttemptDestroyingNonExistentRelation(c *gc.C) { 2600 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2601 s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak")) 2602 endpoints := []string{"riak", "wordpress"} 2603 err := s.serviceApi.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2604 c.Assert(err, gc.ErrorMatches, "no relations found") 2605 } 2606 2607 func (s *serviceSuite) TestAttemptDestroyingWithOnlyOneEndpoint(c *gc.C) { 2608 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2609 endpoints := []string{"wordpress"} 2610 err := s.serviceApi.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2611 c.Assert(err, gc.ErrorMatches, "no relations found") 2612 } 2613 2614 func (s *serviceSuite) TestAttemptDestroyingPeerRelation(c *gc.C) { 2615 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2616 s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak")) 2617 2618 endpoints := []string{"riak:ring"} 2619 err := s.serviceApi.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2620 c.Assert(err, gc.ErrorMatches, `cannot destroy relation "riak:ring": is a peer relation`) 2621 } 2622 2623 func (s *serviceSuite) TestAttemptDestroyingAlreadyDestroyedRelation(c *gc.C) { 2624 s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 2625 2626 // Add a relation between wordpress and mysql. 2627 eps, err := s.State.InferEndpoints("wordpress", "mysql") 2628 c.Assert(err, jc.ErrorIsNil) 2629 rel, err := s.State.AddRelation(eps...) 2630 c.Assert(err, jc.ErrorIsNil) 2631 2632 endpoints := []string{"wordpress", "mysql"} 2633 err = s.serviceApi.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2634 // Show that the relation was removed. 2635 c.Assert(rel.Refresh(), jc.Satisfies, errors.IsNotFound) 2636 2637 // And try to destroy it again. 2638 err = s.serviceApi.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2639 c.Assert(err, gc.ErrorMatches, `relation "wordpress:db mysql:server" not found`) 2640 } 2641 2642 func (s *serviceSuite) TestBlockRemoveDestroyRelation(c *gc.C) { 2643 endpoints := []string{"wordpress", "mysql"} 2644 relation := s.setupDestroyRelationScenario(c, endpoints) 2645 // block remove-objects 2646 s.BlockRemoveObject(c, "TestBlockRemoveDestroyRelation") 2647 err := s.serviceApi.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2648 s.AssertBlocked(c, err, "TestBlockRemoveDestroyRelation") 2649 assertLife(c, relation, state.Alive) 2650 } 2651 2652 func (s *serviceSuite) TestBlockChangeDestroyRelation(c *gc.C) { 2653 endpoints := []string{"wordpress", "mysql"} 2654 relation := s.setupDestroyRelationScenario(c, endpoints) 2655 s.BlockAllChanges(c, "TestBlockChangeDestroyRelation") 2656 err := s.serviceApi.DestroyRelation(params.DestroyRelation{Endpoints: endpoints}) 2657 s.AssertBlocked(c, err, "TestBlockChangeDestroyRelation") 2658 assertLife(c, relation, state.Alive) 2659 } 2660 2661 func (s *serviceSuite) TestBlockDestroyDestroyRelation(c *gc.C) { 2662 s.BlockDestroyModel(c, "TestBlockDestroyDestroyRelation") 2663 endpoints := []string{"wordpress", "mysql"} 2664 s.assertDestroyRelation(c, endpoints) 2665 } 2666 2667 type mockStorageProvider struct { 2668 storage.Provider 2669 kind storage.StorageKind 2670 } 2671 2672 func (m *mockStorageProvider) Scope() storage.Scope { 2673 return storage.ScopeMachine 2674 } 2675 2676 func (m *mockStorageProvider) Supports(k storage.StorageKind) bool { 2677 return k == m.kind 2678 } 2679 2680 func (m *mockStorageProvider) ValidateConfig(*storage.Config) error { 2681 return nil 2682 } 2683 2684 type blobs struct { 2685 sync.Mutex 2686 m map[string]bool // maps path to added (true), or deleted (false) 2687 } 2688 2689 // Add adds a path to the list of known paths. 2690 func (b *blobs) Add(path string) { 2691 b.Lock() 2692 defer b.Unlock() 2693 b.check() 2694 b.m[path] = true 2695 } 2696 2697 // Remove marks a path as deleted, even if it was not previously Added. 2698 func (b *blobs) Remove(path string) { 2699 b.Lock() 2700 defer b.Unlock() 2701 b.check() 2702 b.m[path] = false 2703 } 2704 2705 func (b *blobs) check() { 2706 if b.m == nil { 2707 b.m = make(map[string]bool) 2708 } 2709 } 2710 2711 type recordingStorage struct { 2712 statestorage.Storage 2713 putBarrier *sync.WaitGroup 2714 blobs *blobs 2715 } 2716 2717 func (s *recordingStorage) Put(path string, r io.Reader, size int64) error { 2718 if s.putBarrier != nil { 2719 // This goroutine has gotten to Put() so mark it Done() and 2720 // wait for the other goroutines to get to this point. 2721 s.putBarrier.Done() 2722 s.putBarrier.Wait() 2723 } 2724 if err := s.Storage.Put(path, r, size); err != nil { 2725 return errors.Trace(err) 2726 } 2727 s.blobs.Add(path) 2728 return nil 2729 } 2730 2731 func (s *recordingStorage) Remove(path string) error { 2732 if err := s.Storage.Remove(path); err != nil { 2733 return errors.Trace(err) 2734 } 2735 s.blobs.Remove(path) 2736 return nil 2737 }