github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/charm_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state_test 5 6 import ( 7 "bytes" 8 "fmt" 9 "path/filepath" 10 "strings" 11 12 "github.com/juju/errors" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/charm.v6-unstable" 17 "gopkg.in/macaroon.v1" 18 "gopkg.in/mgo.v2" 19 20 "github.com/juju/juju/state" 21 "github.com/juju/juju/state/storage" 22 "github.com/juju/juju/testcharms" 23 "github.com/juju/juju/testing/factory" 24 ) 25 26 type CharmSuite struct { 27 ConnSuite 28 charm *state.Charm 29 curl *charm.URL 30 } 31 32 var _ = gc.Suite(&CharmSuite{}) 33 34 func (s *CharmSuite) SetUpTest(c *gc.C) { 35 s.ConnSuite.SetUpTest(c) 36 s.charm = s.AddTestingCharm(c, "dummy") 37 s.curl = s.charm.URL() 38 } 39 40 func (s *CharmSuite) destroy(c *gc.C) { 41 err := s.charm.Destroy() 42 c.Assert(err, jc.ErrorIsNil) 43 } 44 45 func (s *CharmSuite) remove(c *gc.C) { 46 s.destroy(c) 47 err := s.charm.Remove() 48 c.Assert(err, jc.ErrorIsNil) 49 } 50 51 func (s *CharmSuite) checkRemoved(c *gc.C) { 52 _, err := s.State.Charm(s.curl) 53 c.Check(err, gc.ErrorMatches, `charm ".*" not found`) 54 c.Check(err, jc.Satisfies, errors.IsNotFound) 55 } 56 57 func (s *CharmSuite) TestAliveCharm(c *gc.C) { 58 s.testCharm(c) 59 } 60 61 func (s *CharmSuite) TestDyingCharm(c *gc.C) { 62 s.destroy(c) 63 s.testCharm(c) 64 } 65 66 func (s *CharmSuite) testCharm(c *gc.C) { 67 dummy, err := s.State.Charm(s.curl) 68 c.Assert(err, jc.ErrorIsNil) 69 c.Assert(dummy.URL().String(), gc.Equals, s.curl.String()) 70 c.Assert(dummy.Revision(), gc.Equals, 1) 71 c.Assert(dummy.StoragePath(), gc.Equals, "dummy-path") 72 c.Assert(dummy.BundleSha256(), gc.Equals, "quantal-dummy-1-sha256") 73 c.Assert(dummy.IsUploaded(), jc.IsTrue) 74 meta := dummy.Meta() 75 c.Assert(meta.Name, gc.Equals, "dummy") 76 config := dummy.Config() 77 c.Assert(config.Options["title"], gc.Equals, 78 charm.Option{ 79 Default: "My Title", 80 Description: "A descriptive title used for the application.", 81 Type: "string", 82 }, 83 ) 84 actions := dummy.Actions() 85 c.Assert(actions, gc.NotNil) 86 c.Assert(actions.ActionSpecs, gc.Not(gc.HasLen), 0) 87 c.Assert(actions.ActionSpecs["snapshot"], gc.NotNil) 88 c.Assert(actions.ActionSpecs["snapshot"].Params, gc.Not(gc.HasLen), 0) 89 c.Assert(actions.ActionSpecs["snapshot"], gc.DeepEquals, 90 charm.ActionSpec{ 91 Description: "Take a snapshot of the database.", 92 Params: map[string]interface{}{ 93 "type": "object", 94 "title": "snapshot", 95 "description": "Take a snapshot of the database.", 96 "properties": map[string]interface{}{ 97 "outfile": map[string]interface{}{ 98 "description": "The file to write out to.", 99 "type": "string", 100 "default": "foo.bz2", 101 }, 102 }, 103 }, 104 }) 105 } 106 107 func (s *CharmSuite) TestRemovedCharmNotFound(c *gc.C) { 108 s.remove(c) 109 s.checkRemoved(c) 110 } 111 112 func (s *CharmSuite) TestRemovedCharmNotListed(c *gc.C) { 113 s.remove(c) 114 charms, err := s.State.AllCharms() 115 c.Check(err, jc.ErrorIsNil) 116 c.Check(charms, gc.HasLen, 0) 117 } 118 119 func (s *CharmSuite) TestRemoveWithoutDestroy(c *gc.C) { 120 err := s.charm.Remove() 121 c.Assert(err, gc.ErrorMatches, "still alive") 122 } 123 124 func (s *CharmSuite) TestCharmNotFound(c *gc.C) { 125 curl := charm.MustParseURL("local:anotherseries/dummy-1") 126 _, err := s.State.Charm(curl) 127 c.Assert(err, gc.ErrorMatches, `charm "local:anotherseries/dummy-1" not found`) 128 c.Assert(err, jc.Satisfies, errors.IsNotFound) 129 } 130 131 func (s *CharmSuite) dummyCharm(c *gc.C, curlOverride string) state.CharmInfo { 132 info := state.CharmInfo{ 133 Charm: testcharms.Repo.CharmDir("dummy"), 134 StoragePath: "dummy-1", 135 SHA256: "dummy-1-sha256", 136 } 137 if curlOverride != "" { 138 info.ID = charm.MustParseURL(curlOverride) 139 } else { 140 info.ID = charm.MustParseURL( 141 fmt.Sprintf("local:quantal/%s-%d", info.Charm.Meta().Name, info.Charm.Revision()), 142 ) 143 } 144 return info 145 } 146 147 func (s *CharmSuite) TestDestroyStoreCharm(c *gc.C) { 148 info := s.dummyCharm(c, "cs:precise/dummy-2") 149 sch, err := s.State.AddCharm(info) 150 c.Assert(err, jc.ErrorIsNil) 151 err = sch.Destroy() 152 c.Assert(err, gc.ErrorMatches, "cannot destroy non-local charms") 153 } 154 155 func (s *CharmSuite) TestRemoveDeletesStorage(c *gc.C) { 156 // We normally don't actually set up charm storage in state 157 // tests, but we need it here. 158 path := s.charm.StoragePath() 159 stor := storage.NewStorage(s.State.ModelUUID(), s.State.MongoSession()) 160 err := stor.Put(path, strings.NewReader("abc"), 3) 161 c.Assert(err, jc.ErrorIsNil) 162 163 s.destroy(c) 164 closer, _, err := stor.Get(path) 165 c.Assert(err, jc.ErrorIsNil) 166 closer.Close() 167 168 s.remove(c) 169 _, _, err = stor.Get(path) 170 c.Assert(err, jc.Satisfies, errors.IsNotFound) 171 } 172 173 func (s *CharmSuite) TestReferenceDyingCharm(c *gc.C) { 174 175 s.destroy(c) 176 177 args := state.AddApplicationArgs{ 178 Name: "blah", 179 Charm: s.charm, 180 } 181 _, err := s.State.AddApplication(args) 182 c.Check(err, gc.ErrorMatches, `cannot add application "blah": charm: not found or not alive`) 183 } 184 185 func (s *CharmSuite) TestReferenceDyingCharmRace(c *gc.C) { 186 187 defer state.SetBeforeHooks(c, s.State, func() { 188 s.destroy(c) 189 }).Check() 190 191 args := state.AddApplicationArgs{ 192 Name: "blah", 193 Charm: s.charm, 194 } 195 _, err := s.State.AddApplication(args) 196 // bad message: see lp:1621754. should match 197 // TestReferenceDyingCharm above. 198 c.Check(err, gc.ErrorMatches, `cannot add application "blah": application already exists`) 199 } 200 201 func (s *CharmSuite) TestDestroyReferencedCharm(c *gc.C) { 202 s.Factory.MakeApplication(c, &factory.ApplicationParams{ 203 Charm: s.charm, 204 }) 205 206 err := s.charm.Destroy() 207 c.Check(err, gc.ErrorMatches, "charm in use") 208 } 209 210 func (s *CharmSuite) TestDestroyReferencedCharmRace(c *gc.C) { 211 212 defer state.SetBeforeHooks(c, s.State, func() { 213 s.Factory.MakeApplication(c, &factory.ApplicationParams{ 214 Charm: s.charm, 215 }) 216 }).Check() 217 218 err := s.charm.Destroy() 219 c.Check(err, gc.ErrorMatches, "charm in use") 220 } 221 222 func (s *CharmSuite) TestDestroyUnreferencedCharm(c *gc.C) { 223 app := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 224 Charm: s.charm, 225 }) 226 err := app.Destroy() 227 c.Assert(err, jc.ErrorIsNil) 228 229 err = s.charm.Destroy() 230 c.Assert(err, jc.ErrorIsNil) 231 } 232 233 func (s *CharmSuite) TestDestroyUnitReferencedCharm(c *gc.C) { 234 app := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 235 Charm: s.charm, 236 }) 237 unit := s.Factory.MakeUnit(c, &factory.UnitParams{ 238 Application: app, 239 SetCharmURL: true, 240 }) 241 242 // set app charm to something different 243 info := s.dummyCharm(c, "cs:quantal/dummy-2") 244 newCh, err := s.State.AddCharm(info) 245 c.Assert(err, jc.ErrorIsNil) 246 err = app.SetCharm(state.SetCharmConfig{Charm: newCh}) 247 c.Assert(err, jc.ErrorIsNil) 248 249 // unit should still reference original charm until updated 250 err = s.charm.Destroy() 251 c.Assert(err, gc.ErrorMatches, "charm in use") 252 err = unit.SetCharmURL(info.ID) 253 c.Assert(err, jc.ErrorIsNil) 254 err = s.charm.Destroy() 255 c.Assert(err, jc.ErrorIsNil) 256 } 257 258 func (s *CharmSuite) TestDestroyFinalUnitReference(c *gc.C) { 259 app := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 260 Charm: s.charm, 261 }) 262 unit := s.Factory.MakeUnit(c, &factory.UnitParams{ 263 Application: app, 264 SetCharmURL: true, 265 }) 266 267 err := app.Destroy() 268 c.Assert(err, jc.ErrorIsNil) 269 removeUnit(c, unit) 270 271 assertCleanupCount(c, s.State, 1) 272 s.checkRemoved(c) 273 } 274 275 func (s *CharmSuite) TestAddCharm(c *gc.C) { 276 // Check that adding charms from scratch works correctly. 277 info := s.dummyCharm(c, "") 278 dummy, err := s.State.AddCharm(info) 279 c.Assert(err, jc.ErrorIsNil) 280 c.Assert(dummy.URL().String(), gc.Equals, info.ID.String()) 281 282 doc := state.CharmDoc{} 283 err = s.charms.FindId(state.DocID(s.State, info.ID.String())).One(&doc) 284 c.Assert(err, jc.ErrorIsNil) 285 c.Logf("%#v", doc) 286 c.Assert(doc.URL, gc.DeepEquals, info.ID) 287 } 288 289 func (s *CharmSuite) TestAddCharmWithAuth(c *gc.C) { 290 // Check that adding charms from scratch works correctly. 291 info := s.dummyCharm(c, "") 292 m, err := macaroon.New([]byte("rootkey"), "id", "loc") 293 c.Assert(err, jc.ErrorIsNil) 294 info.Macaroon = macaroon.Slice{m} 295 dummy, err := s.State.AddCharm(info) 296 c.Assert(err, jc.ErrorIsNil) 297 ms, err := dummy.Macaroon() 298 c.Assert(err, jc.ErrorIsNil) 299 c.Assert(ms, gc.DeepEquals, info.Macaroon) 300 } 301 302 func (s *CharmSuite) TestAddCharmUpdatesPlaceholder(c *gc.C) { 303 // Check that adding charms updates any existing placeholder charm 304 // with the same URL. 305 ch := testcharms.Repo.CharmDir("dummy") 306 307 // Add a placeholder charm. 308 curl := charm.MustParseURL("cs:quantal/dummy-1") 309 err := s.State.AddStoreCharmPlaceholder(curl) 310 c.Assert(err, jc.ErrorIsNil) 311 312 // Add a deployed charm. 313 info := state.CharmInfo{ 314 Charm: ch, 315 ID: curl, 316 StoragePath: "dummy-1", 317 SHA256: "dummy-1-sha256", 318 } 319 dummy, err := s.State.AddCharm(info) 320 c.Assert(err, jc.ErrorIsNil) 321 c.Assert(dummy.URL().String(), gc.Equals, curl.String()) 322 323 // Charm doc has been updated. 324 var docs []state.CharmDoc 325 err = s.charms.FindId(state.DocID(s.State, curl.String())).All(&docs) 326 c.Assert(err, jc.ErrorIsNil) 327 c.Assert(docs, gc.HasLen, 1) 328 c.Assert(docs[0].URL, gc.DeepEquals, curl) 329 c.Assert(docs[0].StoragePath, gc.DeepEquals, info.StoragePath) 330 331 // No more placeholder charm. 332 _, err = s.State.LatestPlaceholderCharm(curl) 333 c.Assert(err, jc.Satisfies, errors.IsNotFound) 334 } 335 336 func (s *CharmSuite) assertPendingCharmExists(c *gc.C, curl *charm.URL) { 337 // Find charm directly and verify only the charm URL and 338 // PendingUpload are set. 339 doc := state.CharmDoc{} 340 err := s.charms.FindId(state.DocID(s.State, curl.String())).One(&doc) 341 c.Assert(err, jc.ErrorIsNil) 342 c.Logf("%#v", doc) 343 c.Assert(doc.URL, gc.DeepEquals, curl) 344 c.Assert(doc.PendingUpload, jc.IsTrue) 345 c.Assert(doc.Placeholder, jc.IsFalse) 346 c.Assert(doc.Meta, gc.IsNil) 347 c.Assert(doc.Config, gc.IsNil) 348 c.Assert(doc.StoragePath, gc.Equals, "") 349 c.Assert(doc.BundleSha256, gc.Equals, "") 350 351 // Make sure we can't find it with st.Charm(). 352 _, err = s.State.Charm(curl) 353 c.Assert(err, jc.Satisfies, errors.IsNotFound) 354 } 355 356 func (s *CharmSuite) TestPrepareLocalCharmUpload(c *gc.C) { 357 // First test the sanity checks. 358 curl, err := s.State.PrepareLocalCharmUpload(charm.MustParseURL("local:quantal/dummy")) 359 c.Assert(err, gc.ErrorMatches, "expected charm URL with revision, got .*") 360 c.Assert(curl, gc.IsNil) 361 curl, err = s.State.PrepareLocalCharmUpload(charm.MustParseURL("cs:quantal/dummy")) 362 c.Assert(err, gc.ErrorMatches, "expected charm URL with local schema, got .*") 363 c.Assert(curl, gc.IsNil) 364 365 // No charm in state, so the call should respect given revision. 366 testCurl := charm.MustParseURL("local:quantal/missing-123") 367 curl, err = s.State.PrepareLocalCharmUpload(testCurl) 368 c.Assert(err, jc.ErrorIsNil) 369 c.Assert(curl, gc.DeepEquals, testCurl) 370 s.assertPendingCharmExists(c, curl) 371 372 // Try adding it again with the same revision and ensure it gets bumped. 373 curl, err = s.State.PrepareLocalCharmUpload(curl) 374 c.Assert(err, jc.ErrorIsNil) 375 c.Assert(curl.Revision, gc.Equals, 124) 376 377 // Also ensure the revision cannot decrease. 378 curl, err = s.State.PrepareLocalCharmUpload(curl.WithRevision(42)) 379 c.Assert(err, jc.ErrorIsNil) 380 c.Assert(curl.Revision, gc.Equals, 125) 381 382 // Check the given revision is respected. 383 curl, err = s.State.PrepareLocalCharmUpload(curl.WithRevision(1234)) 384 c.Assert(err, jc.ErrorIsNil) 385 c.Assert(curl.Revision, gc.Equals, 1234) 386 } 387 388 func (s *CharmSuite) TestPrepareLocalCharmUploadRemoved(c *gc.C) { 389 // Remove the fixture charm and try to re-add it; it gets a new 390 // revision. 391 s.remove(c) 392 curl, err := s.State.PrepareLocalCharmUpload(s.curl) 393 c.Assert(err, jc.ErrorIsNil) 394 c.Assert(curl.Revision, gc.Equals, s.curl.Revision+1) 395 } 396 397 func (s *CharmSuite) TestPrepareStoreCharmUpload(c *gc.C) { 398 // First test the sanity checks. 399 sch, err := s.State.PrepareStoreCharmUpload(charm.MustParseURL("cs:quantal/dummy")) 400 c.Assert(err, gc.ErrorMatches, "expected charm URL with revision, got .*") 401 c.Assert(sch, gc.IsNil) 402 sch, err = s.State.PrepareStoreCharmUpload(charm.MustParseURL("local:quantal/dummy")) 403 c.Assert(err, gc.ErrorMatches, "expected charm URL with cs schema, got .*") 404 c.Assert(sch, gc.IsNil) 405 406 // No charm in state, so the call should respect given revision. 407 testCurl := charm.MustParseURL("cs:quantal/missing-123") 408 sch, err = s.State.PrepareStoreCharmUpload(testCurl) 409 c.Assert(err, jc.ErrorIsNil) 410 c.Assert(sch.URL(), gc.DeepEquals, testCurl) 411 c.Assert(sch.IsUploaded(), jc.IsFalse) 412 413 s.assertPendingCharmExists(c, sch.URL()) 414 415 // Try adding it again with the same revision and ensure we get the same document. 416 schCopy, err := s.State.PrepareStoreCharmUpload(testCurl) 417 c.Assert(err, jc.ErrorIsNil) 418 c.Assert(sch, jc.DeepEquals, schCopy) 419 420 // Now add a charm and try again - we should get the same result 421 // as with AddCharm. 422 info := s.dummyCharm(c, "cs:precise/dummy-2") 423 sch, err = s.State.AddCharm(info) 424 c.Assert(err, jc.ErrorIsNil) 425 schCopy, err = s.State.PrepareStoreCharmUpload(info.ID) 426 c.Assert(err, jc.ErrorIsNil) 427 c.Assert(sch, jc.DeepEquals, schCopy) 428 } 429 430 func (s *CharmSuite) TestUpdateUploadedCharm(c *gc.C) { 431 info := s.dummyCharm(c, "") 432 _, err := s.State.AddCharm(info) 433 c.Assert(err, jc.ErrorIsNil) 434 435 // Test with already uploaded and a missing charms. 436 sch, err := s.State.UpdateUploadedCharm(info) 437 c.Assert(err, gc.ErrorMatches, fmt.Sprintf("charm %q already uploaded", info.ID)) 438 c.Assert(sch, gc.IsNil) 439 info.ID = charm.MustParseURL("local:quantal/missing-1") 440 info.SHA256 = "missing" 441 sch, err = s.State.UpdateUploadedCharm(info) 442 c.Assert(err, jc.Satisfies, errors.IsNotFound) 443 c.Assert(sch, gc.IsNil) 444 445 // Test with with an uploaded local charm. 446 _, err = s.State.PrepareLocalCharmUpload(info.ID) 447 c.Assert(err, jc.ErrorIsNil) 448 449 m, err := macaroon.New([]byte("rootkey"), "id", "loc") 450 c.Assert(err, jc.ErrorIsNil) 451 info.Macaroon = macaroon.Slice{m} 452 c.Assert(err, jc.ErrorIsNil) 453 sch, err = s.State.UpdateUploadedCharm(info) 454 c.Assert(err, jc.ErrorIsNil) 455 c.Assert(sch.URL(), gc.DeepEquals, info.ID) 456 c.Assert(sch.Revision(), gc.Equals, info.ID.Revision) 457 c.Assert(sch.IsUploaded(), jc.IsTrue) 458 c.Assert(sch.IsPlaceholder(), jc.IsFalse) 459 c.Assert(sch.Meta(), gc.DeepEquals, info.Charm.Meta()) 460 c.Assert(sch.Config(), gc.DeepEquals, info.Charm.Config()) 461 c.Assert(sch.StoragePath(), gc.DeepEquals, info.StoragePath) 462 c.Assert(sch.BundleSha256(), gc.Equals, "missing") 463 ms, err := sch.Macaroon() 464 c.Assert(err, jc.ErrorIsNil) 465 c.Assert(ms, gc.DeepEquals, info.Macaroon) 466 } 467 468 func (s *CharmSuite) TestUpdateUploadedCharmEscapesSpecialCharsInConfig(c *gc.C) { 469 // Make sure when we have mongodb special characters like "$" and 470 // "." in the name of any charm config option, we do proper 471 // escaping before storing them and unescaping after loading. See 472 // also http://pad.lv/1308146. 473 474 // Clone the dummy charm and change the config. 475 configWithProblematicKeys := []byte(` 476 options: 477 $bad.key: {default: bad, description: bad, type: string} 478 not.ok.key: {description: not ok, type: int} 479 valid-key: {description: all good, type: boolean} 480 still$bad.: {description: not good, type: float} 481 $.$: {description: awful, type: string} 482 ...: {description: oh boy, type: int} 483 just$: {description: no no, type: float} 484 `[1:]) 485 chDir := testcharms.Repo.ClonedDirPath(c.MkDir(), "dummy") 486 err := utils.AtomicWriteFile( 487 filepath.Join(chDir, "config.yaml"), 488 configWithProblematicKeys, 489 0666, 490 ) 491 c.Assert(err, jc.ErrorIsNil) 492 ch, err := charm.ReadCharmDir(chDir) 493 c.Assert(err, jc.ErrorIsNil) 494 missingCurl := charm.MustParseURL("local:quantal/missing-1") 495 storagePath := "dummy-1" 496 497 preparedCurl, err := s.State.PrepareLocalCharmUpload(missingCurl) 498 c.Assert(err, jc.ErrorIsNil) 499 info := state.CharmInfo{ 500 Charm: ch, 501 ID: preparedCurl, 502 StoragePath: "dummy-1", 503 SHA256: "missing", 504 } 505 sch, err := s.State.UpdateUploadedCharm(info) 506 c.Assert(err, jc.ErrorIsNil) 507 c.Assert(sch.URL(), gc.DeepEquals, missingCurl) 508 c.Assert(sch.Revision(), gc.Equals, missingCurl.Revision) 509 c.Assert(sch.IsUploaded(), jc.IsTrue) 510 c.Assert(sch.IsPlaceholder(), jc.IsFalse) 511 c.Assert(sch.Meta(), gc.DeepEquals, ch.Meta()) 512 c.Assert(sch.Config(), gc.DeepEquals, ch.Config()) 513 c.Assert(sch.StoragePath(), gc.DeepEquals, storagePath) 514 c.Assert(sch.BundleSha256(), gc.Equals, "missing") 515 } 516 517 func (s *CharmSuite) assertPlaceholderCharmExists(c *gc.C, curl *charm.URL) { 518 // Find charm directly and verify only the charm URL and 519 // Placeholder are set. 520 doc := state.CharmDoc{} 521 err := s.charms.FindId(state.DocID(s.State, curl.String())).One(&doc) 522 c.Assert(err, jc.ErrorIsNil) 523 c.Assert(doc.URL, gc.DeepEquals, curl) 524 c.Assert(doc.PendingUpload, jc.IsFalse) 525 c.Assert(doc.Placeholder, jc.IsTrue) 526 c.Assert(doc.Meta, gc.IsNil) 527 c.Assert(doc.Config, gc.IsNil) 528 c.Assert(doc.StoragePath, gc.Equals, "") 529 c.Assert(doc.BundleSha256, gc.Equals, "") 530 531 // Make sure we can't find it with st.Charm(). 532 _, err = s.State.Charm(curl) 533 c.Assert(err, jc.Satisfies, errors.IsNotFound) 534 } 535 536 func (s *CharmSuite) TestLatestPlaceholderCharm(c *gc.C) { 537 // Add a deployed charm 538 info := s.dummyCharm(c, "cs:quantal/dummy-1") 539 _, err := s.State.AddCharm(info) 540 c.Assert(err, jc.ErrorIsNil) 541 542 // Deployed charm not found. 543 _, err = s.State.LatestPlaceholderCharm(info.ID) 544 c.Assert(err, jc.Satisfies, errors.IsNotFound) 545 546 // Add a charm reference 547 curl2 := charm.MustParseURL("cs:quantal/dummy-2") 548 err = s.State.AddStoreCharmPlaceholder(curl2) 549 c.Assert(err, jc.ErrorIsNil) 550 s.assertPlaceholderCharmExists(c, curl2) 551 552 // Use a URL with an arbitrary rev to search. 553 curl := charm.MustParseURL("cs:quantal/dummy-23") 554 pending, err := s.State.LatestPlaceholderCharm(curl) 555 c.Assert(err, jc.ErrorIsNil) 556 c.Assert(pending.URL(), gc.DeepEquals, curl2) 557 c.Assert(pending.IsPlaceholder(), jc.IsTrue) 558 c.Assert(pending.Meta(), gc.IsNil) 559 c.Assert(pending.Config(), gc.IsNil) 560 c.Assert(pending.StoragePath(), gc.Equals, "") 561 c.Assert(pending.BundleSha256(), gc.Equals, "") 562 } 563 564 func (s *CharmSuite) TestAddStoreCharmPlaceholderErrors(c *gc.C) { 565 ch := testcharms.Repo.CharmDir("dummy") 566 curl := charm.MustParseURL( 567 fmt.Sprintf("local:quantal/%s-%d", ch.Meta().Name, ch.Revision()), 568 ) 569 err := s.State.AddStoreCharmPlaceholder(curl) 570 c.Assert(err, gc.ErrorMatches, "expected charm URL with cs schema, got .*") 571 572 curl = charm.MustParseURL("cs:quantal/dummy") 573 err = s.State.AddStoreCharmPlaceholder(curl) 574 c.Assert(err, gc.ErrorMatches, "expected charm URL with revision, got .*") 575 } 576 577 func (s *CharmSuite) TestAddStoreCharmPlaceholder(c *gc.C) { 578 curl := charm.MustParseURL("cs:quantal/dummy-1") 579 err := s.State.AddStoreCharmPlaceholder(curl) 580 c.Assert(err, jc.ErrorIsNil) 581 s.assertPlaceholderCharmExists(c, curl) 582 583 // Add the same one again, should be a no-op 584 err = s.State.AddStoreCharmPlaceholder(curl) 585 c.Assert(err, jc.ErrorIsNil) 586 s.assertPlaceholderCharmExists(c, curl) 587 } 588 589 func (s *CharmSuite) assertAddStoreCharmPlaceholder(c *gc.C) (*charm.URL, *charm.URL, *state.Charm) { 590 // Add a deployed charm 591 info := s.dummyCharm(c, "cs:quantal/dummy-1") 592 dummy, err := s.State.AddCharm(info) 593 c.Assert(err, jc.ErrorIsNil) 594 595 // Add a charm placeholder 596 curl2 := charm.MustParseURL("cs:quantal/dummy-2") 597 err = s.State.AddStoreCharmPlaceholder(curl2) 598 c.Assert(err, jc.ErrorIsNil) 599 s.assertPlaceholderCharmExists(c, curl2) 600 601 // Deployed charm is still there. 602 existing, err := s.State.Charm(info.ID) 603 c.Assert(err, jc.ErrorIsNil) 604 c.Assert(existing, jc.DeepEquals, dummy) 605 606 return info.ID, curl2, dummy 607 } 608 609 func (s *CharmSuite) TestAddStoreCharmPlaceholderLeavesDeployedCharmsAlone(c *gc.C) { 610 s.assertAddStoreCharmPlaceholder(c) 611 } 612 613 func (s *CharmSuite) TestAddStoreCharmPlaceholderDeletesOlder(c *gc.C) { 614 curl, curlOldRef, dummy := s.assertAddStoreCharmPlaceholder(c) 615 616 // Add a new charm placeholder 617 curl3 := charm.MustParseURL("cs:quantal/dummy-3") 618 err := s.State.AddStoreCharmPlaceholder(curl3) 619 c.Assert(err, jc.ErrorIsNil) 620 s.assertPlaceholderCharmExists(c, curl3) 621 622 // Deployed charm is still there. 623 existing, err := s.State.Charm(curl) 624 c.Assert(err, jc.ErrorIsNil) 625 c.Assert(existing, jc.DeepEquals, dummy) 626 627 // Older charm placeholder is gone. 628 doc := state.CharmDoc{} 629 err = s.charms.FindId(curlOldRef).One(&doc) 630 c.Assert(err, gc.Equals, mgo.ErrNotFound) 631 } 632 633 func (s *CharmSuite) TestAllCharms(c *gc.C) { 634 // Add a deployed charm 635 info := s.dummyCharm(c, "cs:quantal/dummy-1") 636 sch, err := s.State.AddCharm(info) 637 c.Assert(err, jc.ErrorIsNil) 638 639 // Add a charm reference 640 curl2 := charm.MustParseURL("cs:quantal/dummy-2") 641 err = s.State.AddStoreCharmPlaceholder(curl2) 642 c.Assert(err, jc.ErrorIsNil) 643 644 charms, err := s.State.AllCharms() 645 c.Assert(err, jc.ErrorIsNil) 646 c.Assert(charms, gc.HasLen, 3) 647 648 c.Assert(charms[0].URL().String(), gc.Equals, "local:quantal/quantal-dummy-1") 649 c.Assert(charms[1], gc.DeepEquals, sch) 650 c.Assert(charms[2].URL(), gc.DeepEquals, curl2) 651 } 652 653 type CharmTestHelperSuite struct { 654 ConnSuite 655 } 656 657 var _ = gc.Suite(&CharmTestHelperSuite{}) 658 659 func assertCustomCharm(c *gc.C, ch *state.Charm, series string, meta *charm.Meta, config *charm.Config, metrics *charm.Metrics, revision int) { 660 // Check Charm interface method results. 661 c.Assert(ch.Meta(), gc.DeepEquals, meta) 662 c.Assert(ch.Config(), gc.DeepEquals, config) 663 c.Assert(ch.Metrics(), gc.DeepEquals, metrics) 664 c.Assert(ch.Revision(), gc.DeepEquals, revision) 665 666 // Test URL matches charm and expected series. 667 url := ch.URL() 668 c.Assert(url.Series, gc.Equals, series) 669 c.Assert(url.Revision, gc.Equals, ch.Revision()) 670 671 // Ignore the StoragePath and BundleSHA256 methods, they're irrelevant. 672 } 673 674 func assertStandardCharm(c *gc.C, ch *state.Charm, series string) { 675 chd := testcharms.Repo.CharmDir(ch.Meta().Name) 676 assertCustomCharm(c, ch, series, chd.Meta(), chd.Config(), chd.Metrics(), chd.Revision()) 677 } 678 679 func forEachStandardCharm(c *gc.C, f func(name string)) { 680 for _, name := range []string{ 681 "logging", "mysql", "riak", "wordpress", 682 } { 683 c.Logf("checking %s", name) 684 f(name) 685 } 686 } 687 688 func (s *CharmTestHelperSuite) TestSimple(c *gc.C) { 689 forEachStandardCharm(c, func(name string) { 690 chd := testcharms.Repo.CharmDir(name) 691 meta := chd.Meta() 692 config := chd.Config() 693 metrics := chd.Metrics() 694 revision := chd.Revision() 695 696 ch := s.AddTestingCharm(c, name) 697 assertCustomCharm(c, ch, "quantal", meta, config, metrics, revision) 698 699 ch = s.AddSeriesCharm(c, name, "anotherseries") 700 assertCustomCharm(c, ch, "anotherseries", meta, config, metrics, revision) 701 }) 702 } 703 704 var configYaml = ` 705 options: 706 working: 707 description: when set to false, prevents service from functioning correctly 708 default: true 709 type: boolean 710 ` 711 712 func (s *CharmTestHelperSuite) TestConfigCharm(c *gc.C) { 713 config, err := charm.ReadConfig(bytes.NewBuffer([]byte(configYaml))) 714 c.Assert(err, jc.ErrorIsNil) 715 716 forEachStandardCharm(c, func(name string) { 717 chd := testcharms.Repo.CharmDir(name) 718 meta := chd.Meta() 719 metrics := chd.Metrics() 720 721 ch := s.AddConfigCharm(c, name, configYaml, 123) 722 assertCustomCharm(c, ch, "quantal", meta, config, metrics, 123) 723 }) 724 } 725 726 var actionsYaml = ` 727 actions: 728 dump: 729 description: Dump the database to STDOUT. 730 params: 731 redirect-file: 732 description: Redirect to a log file. 733 type: string 734 ` 735 736 func (s *CharmTestHelperSuite) TestActionsCharm(c *gc.C) { 737 actions, err := charm.ReadActionsYaml(bytes.NewBuffer([]byte(actionsYaml))) 738 c.Assert(err, jc.ErrorIsNil) 739 740 forEachStandardCharm(c, func(name string) { 741 ch := s.AddActionsCharm(c, name, actionsYaml, 123) 742 c.Assert(ch.Actions(), gc.DeepEquals, actions) 743 }) 744 } 745 746 var metricsYaml = ` 747 metrics: 748 blips: 749 description: A custom metric. 750 type: gauge 751 ` 752 753 func (s *CharmTestHelperSuite) TestMetricsCharm(c *gc.C) { 754 metrics, err := charm.ReadMetrics(bytes.NewBuffer([]byte(metricsYaml))) 755 c.Assert(err, jc.ErrorIsNil) 756 757 forEachStandardCharm(c, func(name string) { 758 chd := testcharms.Repo.CharmDir(name) 759 meta := chd.Meta() 760 config := chd.Config() 761 762 ch := s.AddMetricsCharm(c, name, metricsYaml, 123) 763 assertCustomCharm(c, ch, "quantal", meta, config, metrics, 123) 764 }) 765 } 766 767 var metaYamlSnippet = ` 768 summary: blah 769 description: blah blah 770 ` 771 772 func (s *CharmTestHelperSuite) TestMetaCharm(c *gc.C) { 773 forEachStandardCharm(c, func(name string) { 774 chd := testcharms.Repo.CharmDir(name) 775 config := chd.Config() 776 metrics := chd.Metrics() 777 metaYaml := "name: " + name + metaYamlSnippet 778 meta, err := charm.ReadMeta(bytes.NewBuffer([]byte(metaYaml))) 779 c.Assert(err, jc.ErrorIsNil) 780 781 ch := s.AddMetaCharm(c, name, metaYaml, 123) 782 assertCustomCharm(c, ch, "quantal", meta, config, metrics, 123) 783 }) 784 } 785 786 func (s *CharmTestHelperSuite) TestTestingCharm(c *gc.C) { 787 added := s.AddTestingCharm(c, "metered") 788 c.Assert(added.Metrics(), gc.NotNil) 789 790 chd := testcharms.Repo.CharmDir("metered") 791 c.Assert(chd.Metrics(), gc.DeepEquals, added.Metrics()) 792 }