github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/application/bundle_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package application 5 6 import ( 7 "encoding/base64" 8 "fmt" 9 "io/ioutil" 10 "net/http/httptest" 11 "os" 12 "path/filepath" 13 "runtime" 14 "sort" 15 "strings" 16 "time" 17 18 "github.com/juju/cmd/cmdtesting" 19 "github.com/juju/errors" 20 "github.com/juju/loggo" 21 "github.com/juju/testing" 22 jc "github.com/juju/testing/checkers" 23 gc "gopkg.in/check.v1" 24 "gopkg.in/juju/charm.v6" 25 charmresource "gopkg.in/juju/charm.v6/resource" 26 "gopkg.in/juju/charmrepo.v3" 27 "gopkg.in/juju/charmrepo.v3/csclient" 28 "gopkg.in/juju/names.v2" 29 30 "github.com/juju/juju/api" 31 "github.com/juju/juju/caas/kubernetes/provider" 32 "github.com/juju/juju/controller" 33 "github.com/juju/juju/core/constraints" 34 "github.com/juju/juju/resource" 35 "github.com/juju/juju/state" 36 "github.com/juju/juju/state/multiwatcher" 37 "github.com/juju/juju/state/watcher" 38 "github.com/juju/juju/storage" 39 "github.com/juju/juju/storage/poolmanager" 40 dummystorage "github.com/juju/juju/storage/provider/dummy" 41 "github.com/juju/juju/testcharms" 42 coretesting "github.com/juju/juju/testing" 43 "github.com/juju/juju/testing/factory" 44 ) 45 46 // LTS-dependent requires new entry upon new LTS release. There are numerous 47 // places "xenial" exists in strings throughout this file. If we update the 48 // target in testing/base.go:SetupSuite we'll need to also update the entries 49 // herein. 50 51 func (s *BundleDeployCharmStoreSuite) TestDeployBundleNotFoundCharmStore(c *gc.C) { 52 err := runDeploy(c, "bundle/no-such") 53 c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:bundle/no-such": bundle not found`) 54 } 55 56 func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidFlags(c *gc.C) { 57 testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql") 58 testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress") 59 testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple") 60 err := runDeploy(c, "bundle/wordpress-simple", "--config", "config.yaml") 61 c.Assert(err, gc.ErrorMatches, "options provided but not supported when deploying a bundle: --config") 62 err = runDeploy(c, "bundle/wordpress-simple", "-n", "2") 63 c.Assert(err, gc.ErrorMatches, "options provided but not supported when deploying a bundle: -n") 64 err = runDeploy(c, "bundle/wordpress-simple", "--series", "xenial") 65 c.Assert(err, gc.ErrorMatches, "options provided but not supported when deploying a bundle: --series") 66 } 67 68 func (s *BundleDeployCharmStoreSuite) TestDeployBundleSuccess(c *gc.C) { 69 _, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql") 70 _, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress") 71 testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple") 72 err := runDeploy(c, "bundle/wordpress-simple") 73 c.Assert(err, jc.ErrorIsNil) 74 s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47") 75 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 76 "mysql": {charm: "cs:xenial/mysql-42", config: mysqlch.Config().DefaultSettings()}, 77 "wordpress": {charm: "cs:xenial/wordpress-47", config: wpch.Config().DefaultSettings()}, 78 }) 79 s.assertRelationsEstablished(c, "wordpress:db mysql:server") 80 s.assertUnitsCreated(c, map[string]string{ 81 "mysql/0": "0", 82 "wordpress/0": "1", 83 }) 84 } 85 86 func (s *BundleDeployCharmStoreSuite) TestDeployKubernetesBundleSuccess(c *gc.C) { 87 // Set up a CAAS model to replace the IAAS one. 88 st := s.Factory.MakeCAASModel(c, &factory.ModelParams{ 89 Name: "test", 90 Owner: names.NewUserTag("admin"), 91 }) 92 s.CleanupSuite.AddCleanup(func(*gc.C) { _ = st.Close() }) 93 94 // Close the state pool before the state object itself. 95 c.Assert(s.StatePool.Close(), jc.ErrorIsNil) 96 s.StatePool = nil 97 err := s.State.Close() 98 c.Assert(err, jc.ErrorIsNil) 99 s.State = st 100 101 _, mysqlch := testcharms.UploadCharmWithSeries(c, s.client, "kubernetes/mariadb-42", "mariadb", "kubernetes") 102 _, wpch := testcharms.UploadCharmWithSeries(c, s.client, "kubernetes/gitlab-47", "gitlab", "kubernetes") 103 testcharms.UploadBundle(c, s.client, "bundle/kubernetes-simple-1", "kubernetes-simple") 104 105 settings := state.NewStateSettings(s.State) 106 registry := storage.StaticProviderRegistry{ 107 Providers: map[storage.ProviderType]storage.Provider{ 108 "kubernetes": &dummystorage.StorageProvider{}, 109 }, 110 } 111 pm := poolmanager.New(settings, registry) 112 _, err = pm.Create("operator-storage", provider.K8s_ProviderType, map[string]interface{}{}) 113 c.Assert(err, jc.ErrorIsNil) 114 115 err = runDeploy(c, "-m", "admin/test", "bundle/kubernetes-simple") 116 c.Assert(err, jc.ErrorIsNil) 117 s.assertCharmsUploaded(c, "cs:kubernetes/gitlab-47", "cs:kubernetes/mariadb-42") 118 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 119 "mariadb": {charm: "cs:kubernetes/mariadb-42", config: mysqlch.Config().DefaultSettings()}, 120 "gitlab": {charm: "cs:kubernetes/gitlab-47", config: wpch.Config().DefaultSettings(), placement: "foo=bar", scale: 1}, 121 }) 122 s.assertRelationsEstablished(c, "gitlab:db mariadb:server") 123 } 124 125 func (s *BundleDeployCharmStoreSuite) TestAddMetricCredentials(c *gc.C) { 126 testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql") 127 testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress") 128 testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-plans-1", "wordpress-with-plans") 129 130 deploy := NewDeployCommandForTest( 131 nil, 132 []DeployStep{&RegisterMeteredCharm{PlanURL: s.server.URL, RegisterPath: "", QueryPath: ""}}, 133 ) 134 _, err := cmdtesting.RunCommand(c, deploy, "bundle/wordpress-with-plans") 135 c.Assert(err, jc.ErrorIsNil) 136 137 // The order of calls here does not matter and is, in fact, not guaranteed. 138 // All we care about here is that the calls exist. 139 s.stub.CheckCallsUnordered(c, []testing.StubCall{{ 140 FuncName: "DefaultPlan", 141 Args: []interface{}{"cs:wordpress"}, 142 }, { 143 FuncName: "Authorize", 144 Args: []interface{}{metricRegistrationPost{ 145 ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 146 CharmURL: "cs:wordpress", 147 ApplicationName: "wordpress", 148 PlanURL: "thisplan", 149 IncreaseBudget: 0, 150 }}, 151 }, { 152 FuncName: "Authorize", 153 Args: []interface{}{metricRegistrationPost{ 154 ModelUUID: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 155 CharmURL: "cs:mysql", 156 ApplicationName: "mysql", 157 PlanURL: "test/plan", 158 IncreaseBudget: 0, 159 }}, 160 }}) 161 162 mysqlApp, err := s.State.Application("mysql") 163 c.Assert(err, jc.ErrorIsNil) 164 c.Assert(mysqlApp.MetricCredentials(), jc.DeepEquals, append([]byte(`"aGVsbG8gcmVnaXN0cmF0aW9u"`), 0xA)) 165 166 wordpressApp, err := s.State.Application("wordpress") 167 c.Assert(err, jc.ErrorIsNil) 168 c.Assert(wordpressApp.MetricCredentials(), jc.DeepEquals, append([]byte(`"aGVsbG8gcmVnaXN0cmF0aW9u"`), 0xA)) 169 } 170 171 func (s *BundleDeployCharmStoreSuite) TestDeployBundleWithTermsSuccess(c *gc.C) { 172 _, ch1 := testcharms.UploadCharm(c, s.client, "xenial/terms1-17", "terms1") 173 _, ch2 := testcharms.UploadCharm(c, s.client, "xenial/terms2-42", "terms2") 174 testcharms.UploadBundle(c, s.client, "bundle/terms-simple-1", "terms-simple") 175 err := runDeploy(c, "bundle/terms-simple") 176 c.Assert(err, jc.ErrorIsNil) 177 s.assertCharmsUploaded(c, "cs:xenial/terms1-17", "cs:xenial/terms2-42") 178 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 179 "terms1": {charm: "cs:xenial/terms1-17", config: ch1.Config().DefaultSettings()}, 180 "terms2": {charm: "cs:xenial/terms2-42", config: ch2.Config().DefaultSettings()}, 181 }) 182 s.assertUnitsCreated(c, map[string]string{ 183 "terms1/0": "0", 184 "terms2/0": "1", 185 }) 186 c.Assert(s.termsString, gc.Not(gc.Equals), "") 187 } 188 189 func (s *BundleDeployCharmStoreSuite) TestDeployBundleStorage(c *gc.C) { 190 _, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql-storage") 191 _, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress") 192 testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-mysql-storage-1", "wordpress-with-mysql-storage") 193 err := runDeploy( 194 c, "bundle/wordpress-with-mysql-storage", 195 "--storage", "mysql:logs=tmpfs,10G", // override logs 196 ) 197 c.Assert(err, jc.ErrorIsNil) 198 s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47") 199 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 200 "mysql": { 201 charm: "cs:xenial/mysql-42", 202 config: mysqlch.Config().DefaultSettings(), 203 storage: map[string]state.StorageConstraints{ 204 "data": {Pool: "rootfs", Size: 50 * 1024, Count: 1}, 205 "logs": {Pool: "tmpfs", Size: 10 * 1024, Count: 1}, 206 }, 207 }, 208 "wordpress": {charm: "cs:xenial/wordpress-47", config: wpch.Config().DefaultSettings()}, 209 }) 210 s.assertRelationsEstablished(c, "wordpress:db mysql:server") 211 s.assertUnitsCreated(c, map[string]string{ 212 "mysql/0": "0", 213 "wordpress/0": "1", 214 }) 215 } 216 217 type CAASModelDeployCharmStoreSuite struct { 218 CAASDeploySuiteBase 219 } 220 221 var _ = gc.Suite(&CAASModelDeployCharmStoreSuite{}) 222 223 func (s *CAASModelDeployCharmStoreSuite) TestDeployBundleDevices(c *gc.C) { 224 c.Skip("Test disabled until flakiness is fixed - see bug lp:1781250") 225 226 m, err := s.State.Model() 227 c.Assert(err, jc.ErrorIsNil) 228 229 _, minerCharm := testcharms.UploadCharmWithSeries(c, s.client, "kubernetes/bitcoin-miner-1", "bitcoin-miner", "kubernetes") 230 _, dashboardCharm := testcharms.UploadCharmWithSeries(c, s.client, "kubernetes/dashboard4miner-3", "dashboard4miner", "kubernetes") 231 232 testcharms.UploadBundle(c, s.client, "bundle/bitcoinminer-with-dashboard-1", "bitcoinminer-with-dashboard") 233 err = runDeploy( 234 c, "bundle/bitcoinminer-with-dashboard", 235 "-m", m.Name(), 236 "--device", "miner:bitcoinminer=10,nvidia.com/gpu", // override bitcoinminer 237 ) 238 c.Assert(err, jc.ErrorIsNil) 239 s.assertCharmsUploaded(c, "cs:kubernetes/dashboard4miner-3", "cs:kubernetes/bitcoin-miner-1") 240 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 241 "miner": { 242 charm: "cs:kubernetes/bitcoin-miner-1", 243 config: minerCharm.Config().DefaultSettings(), 244 devices: map[string]state.DeviceConstraints{ 245 "bitcoinminer": {Type: "nvidia.com/gpu", Count: 10, Attributes: map[string]string{}}, 246 }, 247 }, 248 "dashboard": {charm: "cs:kubernetes/dashboard4miner-3", config: dashboardCharm.Config().DefaultSettings()}, 249 }) 250 s.assertRelationsEstablished(c, "dashboard:miner miner:miner") 251 s.assertUnitsCreated(c, map[string]string{ 252 "miner/0": "", 253 "dashboard/0": "", 254 }) 255 } 256 257 func (s *BundleDeployCharmStoreSuite) TestDeployBundleEndpointBindingsSpaceMissing(c *gc.C) { 258 testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql") 259 testcharms.UploadCharm(c, s.client, "xenial/wordpress-extra-bindings-47", "wordpress-extra-bindings") 260 testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-endpoint-bindings-1", "wordpress-with-endpoint-bindings") 261 stdOut, stdErr, err := runDeployWithOutput(c, "bundle/wordpress-with-endpoint-bindings") 262 c.Assert(err, gc.ErrorMatches, ""+ 263 "cannot deploy bundle: cannot deploy application \"mysql\": "+ 264 "cannot add application \"mysql\": unknown space \"db\" not valid") 265 c.Assert(stdErr, gc.Equals, ""+ 266 `Located bundle "cs:bundle/wordpress-with-endpoint-bindings-1"`+"\n"+ 267 "Resolving charm: mysql\n"+ 268 "Resolving charm: wordpress-extra-bindings") 269 c.Assert(stdOut, gc.Equals, ""+ 270 "Executing changes:\n"+ 271 "- upload charm cs:xenial/mysql-42 for series xenial\n"+ 272 "- deploy application mysql on xenial using cs:xenial/mysql-42") 273 s.assertCharmsUploaded(c, "cs:xenial/mysql-42") 274 s.assertApplicationsDeployed(c, map[string]applicationInfo{}) 275 s.assertUnitsCreated(c, map[string]string{}) 276 } 277 278 func (s *BundleDeployCharmStoreSuite) TestDeployBundleEndpointBindingsSuccess(c *gc.C) { 279 _, err := s.State.AddSpace("db", "", nil, false) 280 c.Assert(err, jc.ErrorIsNil) 281 _, err = s.State.AddSpace("public", "", nil, false) 282 c.Assert(err, jc.ErrorIsNil) 283 284 _, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql") 285 _, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-extra-bindings-47", "wordpress-extra-bindings") 286 testcharms.UploadBundle(c, s.client, "bundle/wordpress-with-endpoint-bindings-1", "wordpress-with-endpoint-bindings") 287 err = runDeploy(c, "bundle/wordpress-with-endpoint-bindings") 288 c.Assert(err, jc.ErrorIsNil) 289 s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-extra-bindings-47") 290 291 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 292 "mysql": {charm: "cs:xenial/mysql-42", config: mysqlch.Config().DefaultSettings()}, 293 "wordpress-extra-bindings": {charm: "cs:xenial/wordpress-extra-bindings-47", config: wpch.Config().DefaultSettings()}, 294 }) 295 s.assertDeployedApplicationBindings(c, map[string]applicationInfo{ 296 "mysql": { 297 endpointBindings: map[string]string{"server": "db", "server-admin": "", "metrics-client": ""}, 298 }, 299 "wordpress-extra-bindings": { 300 endpointBindings: map[string]string{ 301 "cache": "", 302 "url": "public", 303 "logging-dir": "", 304 "monitoring-port": "", 305 "db": "db", 306 "cluster": "", 307 "db-client": "db", 308 "admin-api": "public", 309 "foo-bar": "", 310 }, 311 }, 312 }) 313 s.assertRelationsEstablished(c, "wordpress-extra-bindings:cluster", "wordpress-extra-bindings:db mysql:server") 314 s.assertUnitsCreated(c, map[string]string{ 315 "mysql/0": "0", 316 "wordpress-extra-bindings/0": "1", 317 }) 318 } 319 320 func (s *BundleDeployCharmStoreSuite) TestDeployBundleTwice(c *gc.C) { 321 _, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql") 322 _, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress") 323 testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple") 324 stdOut, stdErr, err := runDeployWithOutput(c, "bundle/wordpress-simple") 325 c.Assert(err, jc.ErrorIsNil) 326 c.Check(stdOut, gc.Equals, ""+ 327 "Executing changes:\n"+ 328 "- upload charm cs:xenial/mysql-42 for series xenial\n"+ 329 "- deploy application mysql on xenial using cs:xenial/mysql-42\n"+ 330 "- set annotations for mysql\n"+ 331 "- upload charm cs:xenial/wordpress-47 for series xenial\n"+ 332 "- deploy application wordpress on xenial using cs:xenial/wordpress-47\n"+ 333 "- set annotations for wordpress\n"+ 334 "- add relation wordpress:db - mysql:server\n"+ 335 "- add unit mysql/0 to new machine 0\n"+ 336 "- add unit wordpress/0 to new machine 1", 337 ) 338 stdOut, stdErr, err = runDeployWithOutput(c, "bundle/wordpress-simple") 339 c.Assert(err, jc.ErrorIsNil) 340 // Nothing to do... 341 c.Check(stdOut, gc.Equals, "") 342 c.Check(stdErr, gc.Equals, ""+ 343 "Located bundle \"cs:bundle/wordpress-simple-1\"\n"+ 344 "Resolving charm: mysql\n"+ 345 "Resolving charm: wordpress\n"+ 346 "No changes to apply.", 347 ) 348 349 s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47") 350 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 351 "mysql": {charm: "cs:xenial/mysql-42", config: mysqlch.Config().DefaultSettings()}, 352 "wordpress": {charm: "cs:xenial/wordpress-47", config: wpch.Config().DefaultSettings()}, 353 }) 354 s.assertRelationsEstablished(c, "wordpress:db mysql:server") 355 s.assertUnitsCreated(c, map[string]string{ 356 "mysql/0": "0", 357 "wordpress/0": "1", 358 }) 359 } 360 361 func (s *BundleDeployCharmStoreSuite) TestDryRunTwice(c *gc.C) { 362 testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql") 363 testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress") 364 testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple") 365 stdOut, _, err := runDeployWithOutput(c, "bundle/wordpress-simple", "--dry-run") 366 c.Assert(err, jc.ErrorIsNil) 367 expected := "" + 368 "Changes to deploy bundle:\n" + 369 "- upload charm cs:xenial/mysql-42 for series xenial\n" + 370 "- deploy application mysql on xenial using cs:xenial/mysql-42\n" + 371 "- set annotations for mysql\n" + 372 "- upload charm cs:xenial/wordpress-47 for series xenial\n" + 373 "- deploy application wordpress on xenial using cs:xenial/wordpress-47\n" + 374 "- set annotations for wordpress\n" + 375 "- add relation wordpress:db - mysql:server\n" + 376 "- add unit mysql/0 to new machine 0\n" + 377 "- add unit wordpress/0 to new machine 1" 378 379 c.Check(stdOut, gc.Equals, expected) 380 stdOut, _, err = runDeployWithOutput(c, "bundle/wordpress-simple", "--dry-run") 381 c.Assert(err, jc.ErrorIsNil) 382 c.Check(stdOut, gc.Equals, expected) 383 384 s.assertCharmsUploaded(c /* none */) 385 s.assertApplicationsDeployed(c, map[string]applicationInfo{}) 386 s.assertRelationsEstablished(c /* none */) 387 s.assertUnitsCreated(c, map[string]string{}) 388 } 389 390 func (s *BundleDeployCharmStoreSuite) TestDryRunExistingModel(c *gc.C) { 391 testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql") 392 testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress") 393 testcharms.UploadCharm(c, s.client, "trusty/multi-series-subordinate-13", "multi-series-subordinate") 394 testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple") 395 // Start with a mysql that already has the right charm. 396 ch := s.Factory.MakeCharm(c, &factory.CharmParams{ 397 Name: "mysql", Series: "xenial", Revision: "42"}) 398 mysql := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 399 Name: "mysql", Charm: ch}) 400 s.Factory.MakeUnit(c, &factory.UnitParams{Application: mysql}) 401 // Also add a subordinate that isn't attached to anything. 402 sub := s.Factory.MakeCharm(c, &factory.CharmParams{ 403 Name: "multi-series-subordinate", Series: "trusty", Revision: "13"}) 404 s.Factory.MakeApplication(c, &factory.ApplicationParams{ 405 Name: "sub", Charm: sub}) 406 407 stdOut, _, err := runDeployWithOutput(c, "bundle/wordpress-simple", "--dry-run") 408 c.Assert(err, jc.ErrorIsNil) 409 expected := "" + 410 "Changes to deploy bundle:\n" + 411 "- set annotations for mysql\n" + 412 "- upload charm cs:xenial/wordpress-47 for series xenial\n" + 413 "- deploy application wordpress on xenial using cs:xenial/wordpress-47\n" + 414 "- set annotations for wordpress\n" + 415 "- add relation wordpress:db - mysql:server\n" + 416 "- add unit wordpress/0 to new machine 1" 417 418 c.Check(stdOut, gc.Equals, expected) 419 stdOut, _, err = runDeployWithOutput(c, "bundle/wordpress-simple", "--dry-run") 420 c.Assert(err, jc.ErrorIsNil) 421 c.Check(stdOut, gc.Equals, expected) 422 } 423 424 func (s *BundleDeployCharmStoreSuite) TestDeployBundleGatedCharm(c *gc.C) { 425 _, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql") 426 url, _ := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress") 427 s.changeReadPerm(c, url, clientUserName) 428 testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple") 429 err := runDeploy(c, "bundle/wordpress-simple") 430 c.Assert(err, jc.ErrorIsNil) 431 s.assertCharmsUploaded(c, "cs:xenial/mysql-42", "cs:xenial/wordpress-47") 432 ch, err := s.State.Charm(charm.MustParseURL("cs:xenial/wordpress-47")) 433 c.Assert(err, jc.ErrorIsNil) 434 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 435 "mysql": {charm: "cs:xenial/mysql-42", config: mysqlch.Config().DefaultSettings()}, 436 "wordpress": {charm: "cs:xenial/wordpress-47", config: ch.Config().DefaultSettings()}, 437 }) 438 } 439 440 func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalPath(c *gc.C) { 441 dir := c.MkDir() 442 testcharms.Repo.ClonedDir(dir, "dummy") 443 path := filepath.Join(dir, "mybundle") 444 data := ` 445 series: xenial 446 applications: 447 dummy: 448 charm: ./dummy 449 series: xenial 450 num_units: 1 451 ` 452 err := ioutil.WriteFile(path, []byte(data), 0644) 453 c.Assert(err, jc.ErrorIsNil) 454 err = runDeploy(c, path) 455 c.Assert(err, jc.ErrorIsNil) 456 s.assertCharmsUploaded(c, "local:xenial/dummy-1") 457 ch, err := s.State.Charm(charm.MustParseURL("local:xenial/dummy-1")) 458 c.Assert(err, jc.ErrorIsNil) 459 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 460 "dummy": {charm: "local:xenial/dummy-1", config: ch.Config().DefaultSettings()}, 461 }) 462 } 463 464 func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalResources(c *gc.C) { 465 data := ` 466 series: quantal 467 applications: 468 "dummy-resource": 469 charm: ./dummy-resource 470 series: quantal 471 num_units: 1 472 resources: 473 dummy: ./dummy-resource.zip 474 ` 475 dir := s.makeBundleDir(c, data) 476 testcharms.Repo.ClonedDir(dir, "dummy-resource") 477 c.Assert( 478 ioutil.WriteFile(filepath.Join(dir, "dummy-resource.zip"), []byte("zip file"), 0644), 479 jc.ErrorIsNil) 480 err := runDeploy(c, dir) 481 c.Assert(err, jc.ErrorIsNil) 482 s.assertCharmsUploaded(c, "local:quantal/dummy-resource-0") 483 ch, err := s.State.Charm(charm.MustParseURL("local:quantal/dummy-resource-0")) 484 c.Assert(err, jc.ErrorIsNil) 485 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 486 "dummy-resource": {charm: "local:quantal/dummy-resource-0", config: ch.Config().DefaultSettings()}, 487 }) 488 } 489 490 func (s *BundleDeployCharmStoreSuite) TestDeployBundleNoSeriesInCharmURL(c *gc.C) { 491 testcharms.UploadCharmMultiSeries(c, s.client, "~who/multi-series", "multi-series") 492 dir := c.MkDir() 493 testcharms.Repo.ClonedDir(dir, "dummy") 494 path := filepath.Join(dir, "mybundle") 495 data := ` 496 series: trusty 497 applications: 498 dummy: 499 charm: cs:~who/multi-series 500 ` 501 err := ioutil.WriteFile(path, []byte(data), 0644) 502 c.Assert(err, jc.ErrorIsNil) 503 err = runDeploy(c, path) 504 c.Assert(err, jc.ErrorIsNil) 505 s.assertCharmsUploaded(c, "cs:~who/multi-series-0") 506 ch, err := s.State.Charm(charm.MustParseURL("~who/multi-series-0")) 507 c.Assert(err, jc.ErrorIsNil) 508 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 509 "dummy": {charm: "cs:~who/multi-series-0", config: ch.Config().DefaultSettings()}, 510 }) 511 } 512 513 func (s *BundleDeployCharmStoreSuite) TestDeployBundleGatedCharmUnauthorized(c *gc.C) { 514 testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql") 515 url, _ := testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress") 516 s.changeReadPerm(c, url, "who") 517 testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple") 518 err := runDeploy(c, "bundle/wordpress-simple") 519 c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: .*: access denied for user "client-username"`) 520 } 521 522 func (s *BundleDeployCharmStoreSuite) TestDeployBundleResources(c *gc.C) { 523 testcharms.UploadCharm(c, s.Client(), "trusty/starsay-42", "starsay") 524 bundleMeta := ` 525 applications: 526 starsay: 527 charm: cs:starsay 528 num_units: 1 529 resources: 530 store-resource: 0 531 install-resource: 0 532 upload-resource: 0 533 ` 534 stdOut, stdErr, err := s.DeployBundleYAMLWithOutput(c, bundleMeta) 535 c.Assert(err, jc.ErrorIsNil) 536 537 c.Check(stdOut, gc.Equals, ""+ 538 "Executing changes:\n"+ 539 "- upload charm cs:trusty/starsay-42 for series trusty\n"+ 540 "- deploy application starsay on trusty using cs:trusty/starsay-42\n"+ 541 "- add unit starsay/0 to new machine 0", 542 ) 543 // Info messages go to stdErr. 544 c.Check(stdErr, gc.Equals, ""+ 545 "Resolving charm: cs:starsay\n"+ 546 " added resource install-resource\n"+ 547 " added resource store-resource\n"+ 548 " added resource upload-resource\n"+ 549 "Deploy of bundle completed.", 550 ) 551 552 resourceHash := func(content string) charmresource.Fingerprint { 553 fp, err := charmresource.GenerateFingerprint(strings.NewReader(content)) 554 c.Assert(err, jc.ErrorIsNil) 555 return fp 556 } 557 558 s.checkResources(c, "starsay", []resource.Resource{{ 559 Resource: charmresource.Resource{ 560 Meta: charmresource.Meta{ 561 Name: "install-resource", 562 Type: charmresource.TypeFile, 563 Path: "gotta-have-it.txt", 564 Description: "get things started", 565 }, 566 Origin: charmresource.OriginStore, 567 Revision: 0, 568 Fingerprint: resourceHash("install-resource content"), 569 Size: int64(len("install-resource content")), 570 }, 571 ID: "starsay/install-resource", 572 ApplicationID: "starsay", 573 }, { 574 Resource: charmresource.Resource{ 575 Meta: charmresource.Meta{ 576 Name: "store-resource", 577 Type: charmresource.TypeFile, 578 Path: "filename.tgz", 579 Description: "One line that is useful when operators need to push it.", 580 }, 581 Origin: charmresource.OriginStore, 582 Fingerprint: resourceHash("store-resource content"), 583 Size: int64(len("store-resource content")), 584 Revision: 0, 585 }, 586 ID: "starsay/store-resource", 587 ApplicationID: "starsay", 588 }, { 589 Resource: charmresource.Resource{ 590 Meta: charmresource.Meta{ 591 Name: "upload-resource", 592 Type: charmresource.TypeFile, 593 Path: "somename.xml", 594 Description: "Who uses xml anymore?", 595 }, 596 Origin: charmresource.OriginStore, 597 Fingerprint: resourceHash("upload-resource content"), 598 Size: int64(len("upload-resource content")), 599 Revision: 0, 600 }, 601 ID: "starsay/upload-resource", 602 ApplicationID: "starsay", 603 }}) 604 } 605 606 func (s *BundleDeployCharmStoreSuite) checkResources(c *gc.C, serviceapplication string, expected []resource.Resource) { 607 _, err := s.State.Application("starsay") 608 c.Check(err, jc.ErrorIsNil) 609 st, err := s.State.Resources() 610 c.Assert(err, jc.ErrorIsNil) 611 svcResources, err := st.ListResources("starsay") 612 c.Assert(err, jc.ErrorIsNil) 613 resources := svcResources.Resources 614 resource.Sort(resources) 615 c.Assert(resources, jc.DeepEquals, expected) 616 } 617 618 type BundleDeployCharmStoreSuite struct { 619 charmStoreSuite 620 621 stub *testing.Stub 622 server *httptest.Server 623 } 624 625 var _ = gc.Suite(&BundleDeployCharmStoreSuite{}) 626 627 func (s *BundleDeployCharmStoreSuite) SetUpSuite(c *gc.C) { 628 s.charmStoreSuite.SetUpSuite(c) 629 s.PatchValue(&watcher.Period, 10*time.Millisecond) 630 } 631 632 func (s *BundleDeployCharmStoreSuite) SetUpTest(c *gc.C) { 633 s.stub = &testing.Stub{} 634 handler := &testMetricsRegistrationHandler{Stub: s.stub} 635 s.server = httptest.NewServer(handler) 636 // Set metering URL config so the config is set during bootstrap 637 if s.ControllerConfigAttrs == nil { 638 s.ControllerConfigAttrs = make(map[string]interface{}) 639 } 640 s.ControllerConfigAttrs[controller.MeteringURL] = s.server.URL 641 642 s.charmStoreSuite.SetUpTest(c) 643 logger.SetLogLevel(loggo.TRACE) 644 } 645 646 func (s *BundleDeployCharmStoreSuite) TearDownTest(c *gc.C) { 647 if s.server != nil { 648 s.server.Close() 649 } 650 s.charmStoreSuite.TearDownTest(c) 651 } 652 653 func (s *BundleDeployCharmStoreSuite) Client() *csclient.Client { 654 return s.client 655 } 656 657 // DeployBundleYAML uses the given bundle content to create a bundle in the 658 // local repository and then deploy it. It returns the bundle deployment output 659 // and error. 660 func (s *BundleDeployCharmStoreSuite) DeployBundleYAML(c *gc.C, content string, extraArgs ...string) error { 661 _, _, err := s.DeployBundleYAMLWithOutput(c, content, extraArgs...) 662 return err 663 } 664 665 func (s *BundleDeployCharmStoreSuite) DeployBundleYAMLWithOutput(c *gc.C, content string, extraArgs ...string) (string, string, error) { 666 bundlePath := s.makeBundleDir(c, content) 667 args := append([]string{bundlePath}, extraArgs...) 668 return runDeployWithOutput(c, args...) 669 } 670 671 func (s *BundleDeployCharmStoreSuite) makeBundleDir(c *gc.C, content string) string { 672 bundlePath := filepath.Join(c.MkDir(), "example") 673 c.Assert(os.Mkdir(bundlePath, 0777), jc.ErrorIsNil) 674 err := ioutil.WriteFile(filepath.Join(bundlePath, "bundle.yaml"), []byte(content), 0644) 675 c.Assert(err, jc.ErrorIsNil) 676 err = ioutil.WriteFile(filepath.Join(bundlePath, "README.md"), []byte("README"), 0644) 677 c.Assert(err, jc.ErrorIsNil) 678 return bundlePath 679 } 680 681 var deployBundleErrorsTests = []struct { 682 about string 683 content string 684 err string 685 }{{ 686 about: "local charm not found", 687 content: ` 688 applications: 689 mysql: 690 charm: ./mysql 691 num_units: 1 692 `, 693 err: `the provided bundle has the following errors: 694 charm path in application "mysql" does not exist: .*mysql`, 695 }, { 696 about: "charm store charm not found", 697 content: ` 698 applications: 699 rails: 700 charm: xenial/rails-42 701 num_units: 1 702 `, 703 err: `cannot resolve URL "xenial/rails-42": cannot resolve URL "cs:xenial/rails-42": charm not found`, 704 }, { 705 about: "invalid bundle content", 706 content: "!", 707 err: `(?s)cannot unmarshal bundle data: yaml: .*`, 708 }, { 709 about: "invalid bundle data", 710 content: ` 711 applications: 712 mysql: 713 charm: mysql 714 num_units: -1 715 `, 716 err: `the provided bundle has the following errors: 717 negative number of units specified on application "mysql"`, 718 }, { 719 about: "invalid constraints", 720 content: ` 721 applications: 722 mysql: 723 charm: mysql 724 num_units: 1 725 constraints: bad-wolf 726 `, 727 err: `the provided bundle has the following errors: 728 invalid constraints "bad-wolf" in application "mysql": malformed constraint "bad-wolf"`, 729 }, { 730 about: "multiple bundle verification errors", 731 content: ` 732 applications: 733 mysql: 734 charm: mysql 735 num_units: -1 736 constraints: bad-wolf 737 `, 738 err: `the provided bundle has the following errors: 739 invalid constraints "bad-wolf" in application "mysql": malformed constraint "bad-wolf" 740 negative number of units specified on application "mysql"`, 741 }, { 742 about: "bundle inception", 743 content: ` 744 applications: 745 example: 746 charm: local:wordpress 747 num_units: 1 748 `, 749 err: `cannot resolve URL "local:wordpress": unknown schema for charm URL "local:wordpress"`, 750 }} 751 752 func (s *BundleDeployCharmStoreSuite) TestDeployBundleErrors(c *gc.C) { 753 for i, test := range deployBundleErrorsTests { 754 c.Logf("test %d: %s", i, test.about) 755 err := s.DeployBundleYAML(c, test.content) 756 pass := c.Check(err, gc.ErrorMatches, "cannot deploy bundle: "+test.err) 757 if !pass { 758 c.Logf("error: \n%s\n", errors.ErrorStack(err)) 759 } 760 } 761 } 762 763 func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidOptions(c *gc.C) { 764 testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress") 765 err := s.DeployBundleYAML(c, ` 766 applications: 767 wp: 768 charm: xenial/wordpress-42 769 num_units: 1 770 options: 771 blog-title: 42 772 `) 773 c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot deploy application "wp": option "blog-title" expected string, got 42`) 774 } 775 776 func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidMachineContainerType(c *gc.C) { 777 testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress") 778 err := s.DeployBundleYAML(c, ` 779 applications: 780 wp: 781 charm: xenial/wordpress 782 num_units: 1 783 to: ["bad:1"] 784 machines: 785 1: 786 `) 787 c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot create machine for holding wp unit: invalid container type "bad"`) 788 } 789 790 func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidSeries(c *gc.C) { 791 testcharms.UploadCharm(c, s.client, "vivid/django-0", "dummy") 792 err := s.DeployBundleYAML(c, ` 793 applications: 794 django: 795 charm: vivid/django 796 num_units: 1 797 to: 798 - 1 799 machines: 800 1: 801 series: xenial 802 `) 803 c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot add unit for application "django": acquiring machine to host unit "django/0": cannot assign unit "django/0" to machine 0: series does not match`) 804 } 805 806 func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidBinding(c *gc.C) { 807 testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress") 808 err := s.DeployBundleYAML(c, ` 809 applications: 810 wp: 811 charm: xenial/wordpress-42 812 num_units: 1 813 bindings: 814 noturl: public 815 `) 816 c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot deploy application "wp": invalid binding\(s\) supplied "noturl", valid binding names are "admin-api",.* "url"`) 817 } 818 819 func (s *BundleDeployCharmStoreSuite) TestDeployBundleInvalidSpace(c *gc.C) { 820 testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress") 821 err := s.DeployBundleYAML(c, ` 822 applications: 823 wp: 824 charm: xenial/wordpress-42 825 num_units: 1 826 bindings: 827 url: public 828 `) 829 // TODO(jam): 2017-02-05 double repeating "cannot deploy application" and "cannot add application" is a bit ugly 830 // https://pad.lv/1661937 831 c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot deploy application "wp": cannot add application "wp": unknown space "public" not valid`) 832 } 833 834 func (s *BundleDeployCharmStoreSuite) TestDeployBundleWatcherTimeout(c *gc.C) { 835 // Inject an "AllWatcher" that never delivers a result. 836 ch := make(chan struct{}) 837 defer close(ch) 838 watcher := mockAllWatcher{ 839 next: func() []multiwatcher.Delta { 840 <-ch 841 return nil 842 }, 843 } 844 s.PatchValue(&watchAll, func(*api.Client) (allWatcher, error) { 845 return watcher, nil 846 }) 847 848 testcharms.UploadCharm(c, s.client, "xenial/django-0", "dummy") 849 testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress") 850 s.PatchValue(&updateUnitStatusPeriod, 0*time.Second) 851 err := s.DeployBundleYAML(c, ` 852 applications: 853 django: 854 charm: django 855 num_units: 1 856 wordpress: 857 charm: wordpress 858 num_units: 1 859 to: [django] 860 `) 861 c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot retrieve placement for "wordpress" unit: cannot resolve machine: timeout while trying to get new changes from the watcher`) 862 } 863 864 func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeployment(c *gc.C) { 865 charmsPath := c.MkDir() 866 mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql") 867 wordpressPath := testcharms.Repo.ClonedDirPath(charmsPath, "wordpress") 868 err := s.DeployBundleYAML(c, fmt.Sprintf(` 869 series: xenial 870 applications: 871 wordpress: 872 charm: %s 873 num_units: 1 874 mysql: 875 charm: %s 876 num_units: 2 877 relations: 878 - ["wordpress:db", "mysql:server"] 879 `, wordpressPath, mysqlPath)) 880 c.Assert(err, jc.ErrorIsNil) 881 s.assertCharmsUploaded(c, "local:xenial/mysql-1", "local:xenial/wordpress-3") 882 mysqlch, err := s.State.Charm(charm.MustParseURL("local:xenial/mysql-1")) 883 c.Assert(err, jc.ErrorIsNil) 884 wpch, err := s.State.Charm(charm.MustParseURL("local:xenial/wordpress-3")) 885 c.Assert(err, jc.ErrorIsNil) 886 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 887 "mysql": {charm: "local:xenial/mysql-1", config: mysqlch.Config().DefaultSettings()}, 888 "wordpress": {charm: "local:xenial/wordpress-3", config: wpch.Config().DefaultSettings()}, 889 }) 890 s.assertRelationsEstablished(c, "wordpress:db mysql:server") 891 s.assertUnitsCreated(c, map[string]string{ 892 "mysql/0": "0", 893 "mysql/1": "1", 894 "wordpress/0": "2", 895 }) 896 } 897 898 func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeploymentBadConfig(c *gc.C) { 899 charmsPath := c.MkDir() 900 mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql") 901 wordpressPath := testcharms.Repo.ClonedDirPath(charmsPath, "wordpress") 902 err := s.DeployBundleYAML(c, fmt.Sprintf(` 903 series: xenial 904 applications: 905 wordpress: 906 charm: %s 907 num_units: 1 908 mysql: 909 charm: %s 910 num_units: 2 911 relations: 912 - ["wordpress:db", "mysql:server"] 913 `, wordpressPath, mysqlPath), 914 "--overlay", "missing-file") 915 c.Assert(err, gc.ErrorMatches, "cannot deploy bundle: unable to process overlays: unable to read bundle overlay file .*") 916 } 917 918 func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeploymentLXDProfile(c *gc.C) { 919 charmsPath := c.MkDir() 920 lxdProfilePath := testcharms.Repo.ClonedDirPath(charmsPath, "lxd-profile") 921 err := s.DeployBundleYAML(c, fmt.Sprintf(` 922 series: bionic 923 services: 924 lxd-profile: 925 charm: %s 926 num_units: 1 927 `, lxdProfilePath)) 928 c.Assert(err, jc.ErrorIsNil) 929 s.assertCharmsUploaded(c, "local:bionic/lxd-profile-0") 930 lxdProfile, err := s.State.Charm(charm.MustParseURL("local:bionic/lxd-profile-0")) 931 c.Assert(err, jc.ErrorIsNil) 932 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 933 "lxd-profile": {charm: "local:bionic/lxd-profile-0", config: lxdProfile.Config().DefaultSettings()}, 934 }) 935 s.assertUnitsCreated(c, map[string]string{ 936 "lxd-profile/0": "0", 937 }) 938 } 939 940 func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeploymentBadLXDProfile(c *gc.C) { 941 charmsPath := c.MkDir() 942 lxdProfilePath := testcharms.Repo.ClonedDirPath(charmsPath, "lxd-profile-fail") 943 err := s.DeployBundleYAML(c, fmt.Sprintf(` 944 series: bionic 945 services: 946 lxd-profile-fail: 947 charm: %s 948 num_units: 1 949 `, lxdProfilePath)) 950 c.Assert(err, gc.ErrorMatches, "cannot deploy bundle: cannot deploy local charm at .*: invalid lxd-profile.yaml: contains device type \"unix-disk\"") 951 } 952 953 func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalDeploymentWithBundleOverlay(c *gc.C) { 954 configDir := c.MkDir() 955 configFile := filepath.Join(configDir, "config.yaml") 956 c.Assert( 957 ioutil.WriteFile( 958 configFile, []byte(` 959 applications: 960 wordpress: 961 options: 962 blog-title: include-file://title 963 `), 0644), 964 jc.ErrorIsNil) 965 c.Assert( 966 ioutil.WriteFile( 967 filepath.Join(configDir, "title"), []byte("magic bundle config"), 0644), 968 jc.ErrorIsNil) 969 970 charmsPath := c.MkDir() 971 mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql") 972 wordpressPath := testcharms.Repo.ClonedDirPath(charmsPath, "wordpress") 973 err := s.DeployBundleYAML(c, fmt.Sprintf(` 974 series: xenial 975 applications: 976 wordpress: 977 charm: %s 978 num_units: 1 979 mysql: 980 charm: %s 981 num_units: 2 982 relations: 983 - ["wordpress:db", "mysql:server"] 984 `, wordpressPath, mysqlPath), 985 "--overlay", configFile) 986 987 c.Assert(err, jc.ErrorIsNil) 988 // Now check the blog-title of the wordpress. le") 989 wordpress, err := s.State.Application("wordpress") 990 c.Assert(err, jc.ErrorIsNil) 991 settings, err := wordpress.CharmConfig() 992 c.Assert(err, jc.ErrorIsNil) 993 c.Assert(settings["blog-title"], gc.Equals, "magic bundle config") 994 } 995 996 func (s *BundleDeployCharmStoreSuite) TestDeployBundleLocalAndCharmStoreCharms(c *gc.C) { 997 charmsPath := c.MkDir() 998 _, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress") 999 mysqlPath := testcharms.Repo.ClonedDirPath(charmsPath, "mysql") 1000 err := s.DeployBundleYAML(c, fmt.Sprintf(` 1001 series: xenial 1002 applications: 1003 wordpress: 1004 charm: xenial/wordpress-42 1005 series: xenial 1006 num_units: 1 1007 mysql: 1008 charm: %s 1009 num_units: 1 1010 relations: 1011 - ["wordpress:db", "mysql:server"] 1012 `, mysqlPath)) 1013 c.Assert(err, jc.ErrorIsNil) 1014 s.assertCharmsUploaded(c, "local:xenial/mysql-1", "cs:xenial/wordpress-42") 1015 mysqlch, err := s.State.Charm(charm.MustParseURL("local:xenial/mysql-1")) 1016 c.Assert(err, jc.ErrorIsNil) 1017 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 1018 "mysql": {charm: "local:xenial/mysql-1", config: mysqlch.Config().DefaultSettings()}, 1019 "wordpress": {charm: "cs:xenial/wordpress-42", config: wpch.Config().DefaultSettings()}, 1020 }) 1021 s.assertRelationsEstablished(c, "wordpress:db mysql:server") 1022 s.assertUnitsCreated(c, map[string]string{ 1023 "mysql/0": "0", 1024 "wordpress/0": "1", 1025 }) 1026 } 1027 1028 func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationOptions(c *gc.C) { 1029 _, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress") 1030 _, dch := testcharms.UploadCharm(c, s.client, "precise/dummy-0", "dummy") 1031 err := s.DeployBundleYAML(c, ` 1032 applications: 1033 wordpress: 1034 charm: wordpress 1035 num_units: 1 1036 options: 1037 blog-title: these are the voyages 1038 customized: 1039 charm: precise/dummy-0 1040 num_units: 1 1041 options: 1042 username: who 1043 skill-level: 47 1044 `) 1045 c.Assert(err, jc.ErrorIsNil) 1046 s.assertCharmsUploaded(c, "cs:precise/dummy-0", "cs:xenial/wordpress-42") 1047 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 1048 "customized": { 1049 charm: "cs:precise/dummy-0", 1050 config: s.combinedSettings(dch, charm.Settings{"username": "who", "skill-level": int64(47)}), 1051 }, 1052 "wordpress": { 1053 charm: "cs:xenial/wordpress-42", 1054 config: s.combinedSettings(wpch, charm.Settings{"blog-title": "these are the voyages"}), 1055 }, 1056 }) 1057 s.assertUnitsCreated(c, map[string]string{ 1058 "wordpress/0": "1", 1059 "customized/0": "0", 1060 }) 1061 } 1062 1063 func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationConstraints(c *gc.C) { 1064 _, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress") 1065 _, dch := testcharms.UploadCharm(c, s.client, "precise/dummy-0", "dummy") 1066 err := s.DeployBundleYAML(c, ` 1067 applications: 1068 wordpress: 1069 charm: wordpress 1070 constraints: mem=4G cores=2 1071 customized: 1072 charm: precise/dummy-0 1073 num_units: 1 1074 constraints: arch=i386 1075 `) 1076 c.Assert(err, jc.ErrorIsNil) 1077 s.assertCharmsUploaded(c, "cs:precise/dummy-0", "cs:xenial/wordpress-42") 1078 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 1079 "customized": { 1080 charm: "cs:precise/dummy-0", 1081 constraints: constraints.MustParse("arch=i386"), 1082 config: dch.Config().DefaultSettings(), 1083 }, 1084 "wordpress": { 1085 charm: "cs:xenial/wordpress-42", 1086 constraints: constraints.MustParse("mem=4G cores=2"), 1087 config: wpch.Config().DefaultSettings(), 1088 }, 1089 }) 1090 s.assertUnitsCreated(c, map[string]string{ 1091 "customized/0": "0", 1092 }) 1093 } 1094 1095 func (s *BundleDeployCharmStoreSuite) TestDeployBundleSetAnnotations(c *gc.C) { 1096 testcharms.UploadCharm(c, s.client, "xenial/mysql-42", "mysql") 1097 testcharms.UploadCharm(c, s.client, "xenial/wordpress-47", "wordpress") 1098 testcharms.UploadBundle(c, s.client, "bundle/wordpress-simple-1", "wordpress-simple") 1099 err := runDeploy(c, "bundle/wordpress-simple") 1100 c.Assert(err, jc.ErrorIsNil) 1101 application, err := s.State.Application("wordpress") 1102 c.Assert(err, jc.ErrorIsNil) 1103 ann, err := s.Model.Annotations(application) 1104 c.Assert(err, jc.ErrorIsNil) 1105 c.Assert(ann, jc.DeepEquals, map[string]string{"bundleURL": "cs:bundle/wordpress-simple-1"}) 1106 application2, err := s.State.Application("mysql") 1107 c.Assert(err, jc.ErrorIsNil) 1108 ann2, err := s.Model.Annotations(application2) 1109 c.Assert(err, jc.ErrorIsNil) 1110 c.Assert(ann2, jc.DeepEquals, map[string]string{"bundleURL": "cs:bundle/wordpress-simple-1"}) 1111 } 1112 1113 func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationUpgrade(c *gc.C) { 1114 _, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress") 1115 testcharms.UploadCharm(c, s.client, "vivid/upgrade-1", "upgrade1") 1116 _, ch := testcharms.UploadCharm(c, s.client, "vivid/upgrade-2", "upgrade2") 1117 1118 // First deploy the bundle. 1119 err := s.DeployBundleYAML(c, ` 1120 applications: 1121 wordpress: 1122 charm: wordpress 1123 num_units: 1 1124 options: 1125 blog-title: these are the voyages 1126 constraints: spaces=final,frontiers mem=8000M 1127 up: 1128 charm: vivid/upgrade-1 1129 num_units: 1 1130 constraints: mem=8G 1131 `) 1132 c.Assert(err, jc.ErrorIsNil) 1133 s.assertCharmsUploaded(c, "cs:vivid/upgrade-1", "cs:xenial/wordpress-42") 1134 1135 // Then deploy a new bundle with modified charm revision and options. 1136 stdOut, _, err := s.DeployBundleYAMLWithOutput(c, ` 1137 applications: 1138 wordpress: 1139 charm: wordpress 1140 num_units: 1 1141 options: 1142 blog-title: new title 1143 constraints: spaces=new cores=8 1144 up: 1145 charm: vivid/upgrade-2 1146 num_units: 1 1147 constraints: mem=8G 1148 `) 1149 c.Assert(err, jc.ErrorIsNil) 1150 c.Assert(stdOut, gc.Equals, ""+ 1151 "Executing changes:\n"+ 1152 "- upload charm cs:vivid/upgrade-2 for series vivid\n"+ 1153 "- upgrade up to use charm cs:vivid/upgrade-2 for series vivid\n"+ 1154 "- set application options for wordpress\n"+ 1155 `- set constraints for wordpress to "spaces=new cores=8"`, 1156 ) 1157 1158 s.assertCharmsUploaded(c, "cs:vivid/upgrade-1", "cs:vivid/upgrade-2", "cs:xenial/wordpress-42") 1159 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 1160 "up": { 1161 charm: "cs:vivid/upgrade-2", 1162 config: ch.Config().DefaultSettings(), 1163 constraints: constraints.MustParse("mem=8G"), 1164 }, 1165 "wordpress": { 1166 charm: "cs:xenial/wordpress-42", 1167 config: s.combinedSettings(wpch, charm.Settings{"blog-title": "new title"}), 1168 constraints: constraints.MustParse("spaces=new cores=8"), 1169 }, 1170 }) 1171 s.assertUnitsCreated(c, map[string]string{ 1172 "up/0": "0", 1173 "wordpress/0": "1", 1174 }) 1175 } 1176 1177 func (s *BundleDeployCharmStoreSuite) TestDeployBundleExpose(c *gc.C) { 1178 _, ch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-42", "wordpress") 1179 content := ` 1180 applications: 1181 wordpress: 1182 charm: wordpress 1183 num_units: 1 1184 expose: true 1185 ` 1186 expectedApplications := map[string]applicationInfo{ 1187 "wordpress": { 1188 charm: "cs:xenial/wordpress-42", 1189 config: ch.Config().DefaultSettings(), 1190 exposed: true, 1191 }, 1192 } 1193 1194 // First deploy the bundle. 1195 err := s.DeployBundleYAML(c, content) 1196 c.Assert(err, jc.ErrorIsNil) 1197 s.assertApplicationsDeployed(c, expectedApplications) 1198 1199 // Then deploy the same bundle again: no error is produced when the application 1200 // is exposed again. 1201 stdOut, _, err := s.DeployBundleYAMLWithOutput(c, content) 1202 c.Assert(err, jc.ErrorIsNil) 1203 s.assertApplicationsDeployed(c, expectedApplications) 1204 c.Check(stdOut, gc.Equals, "") // Nothing to do. 1205 1206 // Then deploy a bundle with the application unexposed, and check that the 1207 // application is not unexposed. 1208 stdOut, _, err = s.DeployBundleYAMLWithOutput(c, ` 1209 applications: 1210 wordpress: 1211 charm: wordpress 1212 num_units: 1 1213 expose: false 1214 `) 1215 c.Assert(err, jc.ErrorIsNil) 1216 s.assertApplicationsDeployed(c, expectedApplications) 1217 c.Check(stdOut, gc.Equals, "") // Nothing to do. 1218 } 1219 1220 func (s *BundleDeployCharmStoreSuite) TestDeployBundleApplicationUpgradeFailure(c *gc.C) { 1221 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1222 1223 // Try upgrading to a different series. 1224 // Note that this test comes before the next one because 1225 // otherwise we can't resolve the charm URL because the charm's 1226 // "base entity" is not marked as promulgated so the query by 1227 // promulgated will find it. 1228 testcharms.UploadCharm(c, s.client, "vivid/wordpress-42", "wordpress") 1229 err := s.DeployBundleYAML(c, ` 1230 applications: 1231 wordpress: 1232 charm: vivid/wordpress 1233 num_units: 1 1234 `) 1235 c.Assert(err, gc.ErrorMatches, `cannot deploy bundle: cannot upgrade application "wordpress" to charm "cs:vivid/wordpress-42": cannot change an application's series`) 1236 } 1237 1238 func (s *BundleDeployCharmStoreSuite) TestDeployBundleMultipleRelations(c *gc.C) { 1239 testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress") 1240 testcharms.UploadCharm(c, s.client, "xenial/mysql-1", "mysql") 1241 testcharms.UploadCharm(c, s.client, "xenial/postgres-2", "mysql") 1242 testcharms.UploadCharm(c, s.client, "xenial/varnish-3", "varnish") 1243 err := s.DeployBundleYAML(c, ` 1244 applications: 1245 wp: 1246 charm: wordpress 1247 num_units: 1 1248 mysql: 1249 charm: mysql 1250 num_units: 1 1251 pgres: 1252 charm: xenial/postgres-2 1253 num_units: 1 1254 varnish: 1255 charm: xenial/varnish 1256 num_units: 1 1257 relations: 1258 - ["wp:db", "mysql:server"] 1259 - ["wp:db", "pgres:server"] 1260 - ["varnish:webcache", "wp:cache"] 1261 `) 1262 c.Assert(err, jc.ErrorIsNil) 1263 s.assertRelationsEstablished(c, "wp:db mysql:server", "wp:db pgres:server", "wp:cache varnish:webcache") 1264 s.assertUnitsCreated(c, map[string]string{ 1265 "mysql/0": "0", 1266 "pgres/0": "1", 1267 "varnish/0": "2", 1268 "wp/0": "3", 1269 }) 1270 } 1271 1272 func (s *BundleDeployCharmStoreSuite) TestDeployBundleNewRelations(c *gc.C) { 1273 testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress") 1274 testcharms.UploadCharm(c, s.client, "xenial/mysql-1", "mysql") 1275 testcharms.UploadCharm(c, s.client, "xenial/postgres-2", "mysql") 1276 testcharms.UploadCharm(c, s.client, "xenial/varnish-3", "varnish") 1277 err := s.DeployBundleYAML(c, ` 1278 applications: 1279 wp: 1280 charm: wordpress 1281 num_units: 1 1282 mysql: 1283 charm: mysql 1284 num_units: 1 1285 varnish: 1286 charm: xenial/varnish 1287 num_units: 1 1288 relations: 1289 - ["wp:db", "mysql:server"] 1290 `) 1291 c.Assert(err, jc.ErrorIsNil) 1292 stdOut, _, err := s.DeployBundleYAMLWithOutput(c, ` 1293 applications: 1294 wp: 1295 charm: wordpress 1296 num_units: 1 1297 mysql: 1298 charm: mysql 1299 num_units: 1 1300 varnish: 1301 charm: xenial/varnish 1302 num_units: 1 1303 relations: 1304 - ["wp:db", "mysql:server"] 1305 - ["varnish:webcache", "wp:cache"] 1306 `) 1307 c.Assert(err, jc.ErrorIsNil) 1308 c.Assert(stdOut, gc.Equals, ""+ 1309 "Executing changes:\n"+ 1310 "- add relation varnish:webcache - wp:cache", 1311 ) 1312 s.assertRelationsEstablished(c, "wp:db mysql:server", "wp:cache varnish:webcache") 1313 s.assertUnitsCreated(c, map[string]string{ 1314 "mysql/0": "0", 1315 "varnish/0": "1", 1316 "wp/0": "2", 1317 }) 1318 } 1319 1320 func (s *BundleDeployCharmStoreSuite) TestDeployBundleMachinesUnitsPlacement(c *gc.C) { 1321 _, wpch := testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress") 1322 _, mysqlch := testcharms.UploadCharm(c, s.client, "xenial/mysql-2", "mysql") 1323 1324 content := ` 1325 applications: 1326 wp: 1327 charm: cs:xenial/wordpress-0 1328 num_units: 2 1329 to: 1330 - 1 1331 - lxd:2 1332 options: 1333 blog-title: these are the voyages 1334 sql: 1335 charm: cs:xenial/mysql 1336 num_units: 2 1337 to: 1338 - lxd:wp/0 1339 - new 1340 machines: 1341 1: 1342 series: xenial 1343 2: 1344 ` 1345 err := s.DeployBundleYAML(c, content) 1346 c.Assert(err, jc.ErrorIsNil) 1347 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 1348 "sql": {charm: "cs:xenial/mysql-2", config: mysqlch.Config().DefaultSettings()}, 1349 "wp": { 1350 charm: "cs:xenial/wordpress-0", 1351 config: s.combinedSettings(wpch, charm.Settings{"blog-title": "these are the voyages"}), 1352 }, 1353 }) 1354 s.assertRelationsEstablished(c) 1355 1356 // We explicitly pull out the map creation in the call to 1357 // s.assertUnitsCreated() and create the map as a new variable 1358 // because this /appears/ to tickle a bug on ppc64le using 1359 // gccgo-4.9; the bug is that the map on the receiving side 1360 // does not have the same contents as it does here - which is 1361 // weird because that pattern is used elsewhere in this 1362 // function. And just pulling the map instantiation out of the 1363 // call is not enough; we need to do something benign with the 1364 // variable to keep a reference beyond the call to the 1365 // s.assertUnitsCreated(). I have to chosen to delete a 1366 // non-existent key. This problem does not occur on amd64 1367 // using gc or gccgo-4.9. Nor does it happen using go1.6 on 1368 // ppc64. Once we switch to go1.6 across the board this change 1369 // should be reverted. See http://pad.lv/1556116. 1370 expectedUnits := map[string]string{ 1371 "sql/0": "0/lxd/0", 1372 "sql/1": "2", 1373 "wp/0": "0", 1374 "wp/1": "1/lxd/0", 1375 } 1376 s.assertUnitsCreated(c, expectedUnits) 1377 delete(expectedUnits, "non-existent") 1378 1379 // Redeploy the same bundle again. 1380 err = s.DeployBundleYAML(c, content) 1381 c.Assert(err, jc.ErrorIsNil) 1382 s.assertUnitsCreated(c, map[string]string{ 1383 "sql/0": "0/lxd/0", 1384 "sql/1": "2", 1385 "wp/0": "0", 1386 "wp/1": "1/lxd/0", 1387 }) 1388 } 1389 1390 func (s *BundleDeployCharmStoreSuite) TestLXCTreatedAsLXD(c *gc.C) { 1391 testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress") 1392 1393 // Note that we use lxc here, to represent a 1.x bundle that specifies lxc. 1394 content := ` 1395 applications: 1396 wp: 1397 charm: cs:xenial/wordpress-0 1398 num_units: 1 1399 to: 1400 - lxc:0 1401 options: 1402 blog-title: these are the voyages 1403 wp2: 1404 charm: cs:xenial/wordpress-0 1405 num_units: 1 1406 to: 1407 - lxc:0 1408 options: 1409 blog-title: these are the voyages 1410 machines: 1411 0: 1412 series: xenial 1413 ` 1414 _, output, err := s.DeployBundleYAMLWithOutput(c, content) 1415 c.Assert(err, jc.ErrorIsNil) 1416 expectedUnits := map[string]string{ 1417 "wp/0": "0/lxd/0", 1418 "wp2/0": "0/lxd/1", 1419 } 1420 idx := strings.Index(output, "Bundle has one or more containers specified as lxc. lxc containers are deprecated in Juju 2.0. lxd containers will be deployed instead.") 1421 lastIdx := strings.LastIndex(output, "Bundle has one or more containers specified as lxc. lxc containers are deprecated in Juju 2.0. lxd containers will be deployed instead.") 1422 // The message exists. 1423 c.Assert(idx, jc.GreaterThan, -1) 1424 // No more than one instance of the message was printed. 1425 c.Assert(idx, gc.Equals, lastIdx) 1426 s.assertUnitsCreated(c, expectedUnits) 1427 } 1428 1429 func (s *BundleDeployCharmStoreSuite) TestDeployBundleMachineAttributes(c *gc.C) { 1430 _, ch := testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy") 1431 err := s.DeployBundleYAML(c, ` 1432 applications: 1433 django: 1434 charm: cs:xenial/django-42 1435 num_units: 2 1436 to: 1437 - 1 1438 - new 1439 machines: 1440 1: 1441 series: xenial 1442 constraints: "cores=4 mem=4G" 1443 annotations: 1444 foo: bar 1445 `) 1446 c.Assert(err, jc.ErrorIsNil) 1447 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 1448 "django": {charm: "cs:xenial/django-42", config: ch.Config().DefaultSettings()}, 1449 }) 1450 s.assertRelationsEstablished(c) 1451 s.assertUnitsCreated(c, map[string]string{ 1452 "django/0": "0", 1453 "django/1": "1", 1454 }) 1455 m, err := s.State.Machine("0") 1456 c.Assert(err, jc.ErrorIsNil) 1457 c.Assert(m.Series(), gc.Equals, "xenial") 1458 cons, err := m.Constraints() 1459 c.Assert(err, jc.ErrorIsNil) 1460 expectedCons, err := constraints.Parse("cores=4 mem=4G") 1461 c.Assert(err, jc.ErrorIsNil) 1462 c.Assert(cons, jc.DeepEquals, expectedCons) 1463 ann, err := s.Model.Annotations(m) 1464 c.Assert(err, jc.ErrorIsNil) 1465 c.Assert(ann, jc.DeepEquals, map[string]string{"foo": "bar"}) 1466 } 1467 1468 func (s *BundleDeployCharmStoreSuite) TestDeployBundleTwiceScaleUp(c *gc.C) { 1469 testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy") 1470 err := s.DeployBundleYAML(c, ` 1471 applications: 1472 django: 1473 charm: cs:xenial/django-42 1474 num_units: 2 1475 `) 1476 c.Assert(err, jc.ErrorIsNil) 1477 err = s.DeployBundleYAML(c, ` 1478 applications: 1479 django: 1480 charm: cs:xenial/django-42 1481 num_units: 5 1482 `) 1483 c.Assert(err, jc.ErrorIsNil) 1484 s.assertUnitsCreated(c, map[string]string{ 1485 "django/0": "0", 1486 "django/1": "1", 1487 "django/2": "2", 1488 "django/3": "3", 1489 "django/4": "4", 1490 }) 1491 } 1492 1493 func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitPlacedInApplication(c *gc.C) { 1494 testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy") 1495 testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress") 1496 err := s.DeployBundleYAML(c, ` 1497 applications: 1498 wordpress: 1499 charm: wordpress 1500 num_units: 3 1501 django: 1502 charm: cs:xenial/django-42 1503 num_units: 2 1504 to: [wordpress] 1505 `) 1506 c.Assert(err, jc.ErrorIsNil) 1507 s.assertUnitsCreated(c, map[string]string{ 1508 "django/0": "0", 1509 "django/1": "1", 1510 "wordpress/0": "0", 1511 "wordpress/1": "1", 1512 "wordpress/2": "2", 1513 }) 1514 } 1515 1516 func (s *BundleDeployCharmStoreSuite) TestDeployBundlePeerContainer(c *gc.C) { 1517 testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy") 1518 testcharms.UploadCharm(c, s.client, "xenial/wordpress-0", "wordpress") 1519 1520 stdOut, _, err := s.DeployBundleYAMLWithOutput(c, ` 1521 applications: 1522 wordpress: 1523 charm: wordpress 1524 num_units: 2 1525 to: ["lxd:new"] 1526 django: 1527 charm: cs:xenial/django-42 1528 num_units: 2 1529 to: ["lxd:wordpress"] 1530 `) 1531 c.Assert(err, jc.ErrorIsNil) 1532 c.Check(stdOut, gc.Equals, ""+ 1533 "Executing changes:\n"+ 1534 "- upload charm cs:xenial/django-42 for series xenial\n"+ 1535 "- deploy application django on xenial using cs:xenial/django-42\n"+ 1536 "- upload charm cs:xenial/wordpress-0 for series xenial\n"+ 1537 "- deploy application wordpress on xenial using cs:xenial/wordpress-0\n"+ 1538 "- add lxd container 0/lxd/0 on new machine 0\n"+ 1539 "- add lxd container 1/lxd/0 on new machine 1\n"+ 1540 "- add unit wordpress/0 to 0/lxd/0\n"+ 1541 "- add unit wordpress/1 to 1/lxd/0\n"+ 1542 "- add lxd container 0/lxd/1 on new machine 0\n"+ 1543 "- add lxd container 1/lxd/1 on new machine 1\n"+ 1544 "- add unit django/0 to 0/lxd/1 to satisfy [lxd:wordpress]\n"+ 1545 "- add unit django/1 to 1/lxd/1 to satisfy [lxd:wordpress]", 1546 ) 1547 1548 s.assertUnitsCreated(c, map[string]string{ 1549 "django/0": "0/lxd/1", 1550 "django/1": "1/lxd/1", 1551 "wordpress/0": "0/lxd/0", 1552 "wordpress/1": "1/lxd/0", 1553 }) 1554 } 1555 1556 func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitColocationWithUnit(c *gc.C) { 1557 testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy") 1558 testcharms.UploadCharm(c, s.client, "xenial/mem-47", "dummy") 1559 testcharms.UploadCharm(c, s.client, "xenial/rails-0", "dummy") 1560 err := s.DeployBundleYAML(c, ` 1561 applications: 1562 memcached: 1563 charm: cs:xenial/mem-47 1564 num_units: 3 1565 to: [1, new] 1566 django: 1567 charm: cs:xenial/django-42 1568 num_units: 5 1569 to: 1570 - memcached/0 1571 - lxd:memcached/1 1572 - lxd:memcached/2 1573 - kvm:ror 1574 ror: 1575 charm: rails 1576 num_units: 2 1577 to: 1578 - new 1579 - 1 1580 machines: 1581 1: 1582 series: xenial 1583 `) 1584 c.Assert(err, jc.ErrorIsNil) 1585 s.assertUnitsCreated(c, map[string]string{ 1586 "django/0": "0", 1587 "django/1": "1/lxd/0", 1588 "django/2": "2/lxd/0", 1589 "django/3": "3/kvm/0", 1590 "django/4": "0/kvm/0", 1591 "memcached/0": "0", 1592 "memcached/1": "1", 1593 "memcached/2": "2", 1594 "ror/0": "3", 1595 "ror/1": "0", 1596 }) 1597 } 1598 1599 func (s *BundleDeployCharmStoreSuite) TestDeployBundleUnitPlacedToMachines(c *gc.C) { 1600 testcharms.UploadCharm(c, s.client, "bionic/django-42", "dummy") 1601 err := s.DeployBundleYAML(c, ` 1602 applications: 1603 django: 1604 charm: cs:django 1605 num_units: 7 1606 to: 1607 - new 1608 - 4 1609 - kvm:8 1610 - lxd:4 1611 - lxd:4 1612 - lxd:new 1613 machines: 1614 4: 1615 8: 1616 `) 1617 c.Assert(err, jc.ErrorIsNil) 1618 s.assertUnitsCreated(c, map[string]string{ 1619 "django/0": "2", // Machine "new" in the bundle. 1620 "django/1": "0", // Machine "4" in the bundle. 1621 "django/2": "1/kvm/0", // The KVM container in bundle machine "8". 1622 "django/3": "0/lxd/0", // First lxd container in bundle machine "4". 1623 "django/4": "0/lxd/1", // Second lxd container in bundle machine "4". 1624 "django/5": "3/lxd/0", // First lxd in new machine. 1625 "django/6": "4/lxd/0", // Second lxd in new machine. 1626 }) 1627 } 1628 1629 func (s *BundleDeployCharmStoreSuite) TestDeployBundleMassiveUnitColocation(c *gc.C) { 1630 testcharms.UploadCharm(c, s.client, "bionic/django-42", "dummy") 1631 testcharms.UploadCharm(c, s.client, "bionic/mem-47", "dummy") 1632 testcharms.UploadCharm(c, s.client, "bionic/rails-0", "dummy") 1633 err := s.DeployBundleYAML(c, ` 1634 applications: 1635 memcached: 1636 charm: cs:bionic/mem-47 1637 num_units: 3 1638 to: [1, 2, 3] 1639 django: 1640 charm: cs:bionic/django-42 1641 num_units: 4 1642 to: 1643 - 1 1644 - lxd:memcached 1645 ror: 1646 charm: rails 1647 num_units: 3 1648 to: 1649 - 1 1650 - kvm:3 1651 machines: 1652 1: 1653 2: 1654 3: 1655 `) 1656 c.Assert(err, jc.ErrorIsNil) 1657 s.assertUnitsCreated(c, map[string]string{ 1658 "django/0": "0", 1659 "django/1": "0/lxd/0", 1660 "django/2": "1/lxd/0", 1661 "django/3": "2/lxd/0", 1662 "memcached/0": "0", 1663 "memcached/1": "1", 1664 "memcached/2": "2", 1665 "ror/0": "0", 1666 "ror/1": "2/kvm/0", 1667 "ror/2": "3", 1668 }) 1669 1670 // Redeploy a very similar bundle with another application unit. The new unit 1671 // is placed on the first unit of memcached. Due to ordering of the applications 1672 // there is no deterministic way to determine "least crowded" in a meaningful way. 1673 content := ` 1674 applications: 1675 memcached: 1676 charm: cs:bionic/mem-47 1677 num_units: 3 1678 to: [1, 2, 3] 1679 django: 1680 charm: cs:bionic/django-42 1681 num_units: 4 1682 to: 1683 - 1 1684 - lxd:memcached 1685 node: 1686 charm: cs:bionic/django-42 1687 num_units: 1 1688 to: 1689 - lxd:memcached 1690 machines: 1691 1: 1692 2: 1693 3: 1694 ` 1695 stdOut, _, err := s.DeployBundleYAMLWithOutput(c, content) 1696 c.Assert(err, jc.ErrorIsNil) 1697 c.Check(stdOut, gc.Equals, ""+ 1698 "Executing changes:\n"+ 1699 "- deploy application node on bionic using cs:bionic/django-42\n"+ 1700 "- add unit node/0 to 0/lxd/0 to satisfy [lxd:memcached]", 1701 ) 1702 1703 // Redeploy the same bundle again and check that nothing happens. 1704 stdOut, _, err = s.DeployBundleYAMLWithOutput(c, content) 1705 c.Assert(err, jc.ErrorIsNil) 1706 c.Assert(stdOut, gc.Equals, "") 1707 s.assertUnitsCreated(c, map[string]string{ 1708 "django/0": "0", 1709 "django/1": "0/lxd/0", 1710 "django/2": "1/lxd/0", 1711 "django/3": "2/lxd/0", 1712 "memcached/0": "0", 1713 "memcached/1": "1", 1714 "memcached/2": "2", 1715 "node/0": "0/lxd/1", 1716 "ror/0": "0", 1717 "ror/1": "2/kvm/0", 1718 "ror/2": "3", 1719 }) 1720 } 1721 1722 func (s *BundleDeployCharmStoreSuite) TestDeployBundleWithAnnotations_OutputIsCorrect(c *gc.C) { 1723 testcharms.UploadCharm(c, s.client, "bionic/django-42", "dummy") 1724 testcharms.UploadCharm(c, s.client, "bionic/mem-47", "dummy") 1725 stdOut, stdErr, err := s.DeployBundleYAMLWithOutput(c, ` 1726 applications: 1727 django: 1728 charm: cs:django 1729 num_units: 1 1730 annotations: 1731 key1: value1 1732 key2: value2 1733 to: [1] 1734 memcached: 1735 charm: bionic/mem-47 1736 num_units: 1 1737 machines: 1738 1: 1739 annotations: {foo: bar} 1740 `) 1741 c.Assert(err, jc.ErrorIsNil) 1742 1743 c.Check(stdOut, gc.Equals, ""+ 1744 "Executing changes:\n"+ 1745 "- upload charm cs:bionic/django-42 for series bionic\n"+ 1746 "- deploy application django on bionic using cs:bionic/django-42\n"+ 1747 "- set annotations for django\n"+ 1748 "- upload charm cs:bionic/mem-47 for series bionic\n"+ 1749 "- deploy application memcached on bionic using cs:bionic/mem-47\n"+ 1750 "- add new machine 0 (bundle machine 1)\n"+ 1751 "- set annotations for new machine 0\n"+ 1752 "- add unit django/0 to new machine 0\n"+ 1753 "- add unit memcached/0 to new machine 1", 1754 ) 1755 c.Check(stdErr, gc.Equals, ""+ 1756 "Resolving charm: cs:django\n"+ 1757 "Resolving charm: bionic/mem-47\n"+ 1758 "Deploy of bundle completed.", 1759 ) 1760 } 1761 1762 func (s *BundleDeployCharmStoreSuite) TestDeployBundleAnnotations(c *gc.C) { 1763 testcharms.UploadCharm(c, s.client, "bionic/django-42", "dummy") 1764 testcharms.UploadCharm(c, s.client, "bionic/mem-47", "dummy") 1765 err := s.DeployBundleYAML(c, ` 1766 applications: 1767 django: 1768 charm: cs:django 1769 num_units: 1 1770 annotations: 1771 key1: value1 1772 key2: value2 1773 to: [1] 1774 memcached: 1775 charm: bionic/mem-47 1776 num_units: 1 1777 machines: 1778 1: 1779 annotations: {foo: bar} 1780 `) 1781 c.Assert(err, jc.ErrorIsNil) 1782 svc, err := s.State.Application("django") 1783 c.Assert(err, jc.ErrorIsNil) 1784 ann, err := s.Model.Annotations(svc) 1785 c.Assert(err, jc.ErrorIsNil) 1786 c.Assert(ann, jc.DeepEquals, map[string]string{ 1787 "key1": "value1", 1788 "key2": "value2", 1789 }) 1790 m, err := s.State.Machine("0") 1791 c.Assert(err, jc.ErrorIsNil) 1792 ann, err = s.Model.Annotations(m) 1793 c.Assert(err, jc.ErrorIsNil) 1794 c.Assert(ann, jc.DeepEquals, map[string]string{"foo": "bar"}) 1795 1796 // Update the annotations and deploy the bundle again. 1797 err = s.DeployBundleYAML(c, ` 1798 applications: 1799 django: 1800 charm: cs:django 1801 num_units: 1 1802 annotations: 1803 key1: new value! 1804 key2: value2 1805 to: [1] 1806 machines: 1807 1: 1808 annotations: {answer: 42} 1809 `) 1810 c.Assert(err, jc.ErrorIsNil) 1811 ann, err = s.Model.Annotations(svc) 1812 c.Assert(err, jc.ErrorIsNil) 1813 c.Assert(ann, jc.DeepEquals, map[string]string{ 1814 "key1": "new value!", 1815 "key2": "value2", 1816 }) 1817 ann, err = s.Model.Annotations(m) 1818 c.Assert(err, jc.ErrorIsNil) 1819 c.Assert(ann, jc.DeepEquals, map[string]string{ 1820 "foo": "bar", 1821 "answer": "42", 1822 }) 1823 } 1824 1825 func (s *BundleDeployCharmStoreSuite) TestDeployBundleExistingMachines(c *gc.C) { 1826 xenialMachine := &factory.MachineParams{Series: "xenial"} 1827 s.Factory.MakeMachine(c, xenialMachine) // machine-0 1828 s.Factory.MakeMachine(c, xenialMachine) // machine-1 1829 s.Factory.MakeMachine(c, xenialMachine) // machine-2 1830 s.Factory.MakeMachine(c, xenialMachine) // machine-3 1831 testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy") 1832 err := s.DeployBundleYAML(c, ` 1833 applications: 1834 django: 1835 charm: cs:django 1836 num_units: 3 1837 to: [0,1,2] 1838 machines: 1839 0: 1840 1: 1841 2: 1842 `, "--map-machines", "existing,2=3") 1843 c.Assert(err, jc.ErrorIsNil) 1844 s.assertUnitsCreated(c, map[string]string{ 1845 "django/0": "0", 1846 "django/1": "1", 1847 "django/2": "3", 1848 }) 1849 } 1850 1851 type mockAllWatcher struct { 1852 next func() []multiwatcher.Delta 1853 } 1854 1855 func (w mockAllWatcher) Next() ([]multiwatcher.Delta, error) { 1856 return w.next(), nil 1857 } 1858 1859 func (mockAllWatcher) Stop() error { 1860 return nil 1861 } 1862 1863 type ProcessIncludesSuite struct { 1864 coretesting.BaseSuite 1865 } 1866 1867 var _ = gc.Suite(&ProcessIncludesSuite{}) 1868 1869 func (*ProcessIncludesSuite) TestNonString(c *gc.C) { 1870 value := 1234 1871 result, changed, err := processValue("", value) 1872 1873 c.Check(err, jc.ErrorIsNil) 1874 c.Check(changed, jc.IsFalse) 1875 c.Check(result, gc.Equals, value) 1876 } 1877 1878 func (*ProcessIncludesSuite) TestSimpleString(c *gc.C) { 1879 value := "simple" 1880 result, changed, err := processValue("", value) 1881 1882 c.Check(err, jc.ErrorIsNil) 1883 c.Check(changed, jc.IsFalse) 1884 c.Check(result, gc.Equals, value) 1885 } 1886 1887 func (*ProcessIncludesSuite) TestMissingFile(c *gc.C) { 1888 value := "include-file://simple" 1889 result, changed, err := processValue("", value) 1890 1891 c.Check(err, gc.ErrorMatches, "unable to read file: "+missingFileRegex("simple")) 1892 c.Check(changed, jc.IsFalse) 1893 c.Check(result, gc.IsNil) 1894 } 1895 1896 func (*ProcessIncludesSuite) TestFileNameIsInDir(c *gc.C) { 1897 dir := c.MkDir() 1898 filename := filepath.Join(dir, "content") 1899 err := ioutil.WriteFile(filename, []byte("testing"), 0644) 1900 c.Assert(err, jc.ErrorIsNil) 1901 1902 value := "include-file://content" 1903 result, changed, err := processValue(dir, value) 1904 1905 c.Assert(err, jc.ErrorIsNil) 1906 c.Check(changed, jc.IsTrue) 1907 c.Check(result, gc.Equals, "testing") 1908 } 1909 1910 func (*ProcessIncludesSuite) TestRelativePath(c *gc.C) { 1911 dir := c.MkDir() 1912 c.Assert(os.Mkdir(filepath.Join(dir, "nested"), 0755), jc.ErrorIsNil) 1913 1914 filename := filepath.Join(dir, "nested", "content") 1915 err := ioutil.WriteFile(filename, []byte("testing"), 0644) 1916 c.Assert(err, jc.ErrorIsNil) 1917 1918 value := "include-file://./nested/content" 1919 result, changed, err := processValue(dir, value) 1920 1921 c.Assert(err, jc.ErrorIsNil) 1922 c.Check(changed, jc.IsTrue) 1923 c.Check(result, gc.Equals, "testing") 1924 } 1925 1926 func (*ProcessIncludesSuite) TestAbsolutePath(c *gc.C) { 1927 dir := c.MkDir() 1928 c.Assert(os.Mkdir(filepath.Join(dir, "nested"), 0755), jc.ErrorIsNil) 1929 1930 filename := filepath.Join(dir, "nested", "content") 1931 c.Check(filepath.IsAbs(filename), jc.IsTrue) 1932 err := ioutil.WriteFile(filename, []byte("testing"), 0644) 1933 c.Assert(err, jc.ErrorIsNil) 1934 1935 value := "include-file://" + filename 1936 result, changed, err := processValue(dir, value) 1937 1938 c.Assert(err, jc.ErrorIsNil) 1939 c.Check(changed, jc.IsTrue) 1940 c.Check(result, gc.Equals, "testing") 1941 } 1942 1943 func (*ProcessIncludesSuite) TestBase64Encode(c *gc.C) { 1944 dir := c.MkDir() 1945 filename := filepath.Join(dir, "content") 1946 err := ioutil.WriteFile(filename, []byte("testing"), 0644) 1947 c.Assert(err, jc.ErrorIsNil) 1948 1949 value := "include-base64://content" 1950 result, changed, err := processValue(dir, value) 1951 1952 c.Assert(err, jc.ErrorIsNil) 1953 c.Check(changed, jc.IsTrue) 1954 encoded := base64.StdEncoding.EncodeToString([]byte("testing")) 1955 c.Check(result, gc.Equals, encoded) 1956 } 1957 1958 func (*ProcessIncludesSuite) TestBundleReplacements(c *gc.C) { 1959 bundleYAML := ` 1960 applications: 1961 django: 1962 charm: cs:django 1963 num_units: 1 1964 options: 1965 private: include-base64://sekrit.binary 1966 annotations: 1967 key1: value1 1968 key2: value2 1969 key3: include-file://annotation 1970 to: [1] 1971 memcached: 1972 charm: xenial/mem-47 1973 num_units: 1 1974 machines: 1975 1: 1976 annotations: {foo: bar, baz: "include-file://machine" } 1977 2: 1978 ` 1979 1980 baseDir := c.MkDir() 1981 bundleFile := filepath.Join(baseDir, "bundle.yaml") 1982 err := ioutil.WriteFile(bundleFile, []byte(bundleYAML), 0644) 1983 c.Assert(err, jc.ErrorIsNil) 1984 1985 c.Assert( 1986 ioutil.WriteFile( 1987 filepath.Join(baseDir, "sekrit.binary"), 1988 []byte{42, 12, 0, 23, 8}, 0644), 1989 jc.ErrorIsNil) 1990 c.Assert( 1991 ioutil.WriteFile( 1992 filepath.Join(baseDir, "annotation"), 1993 []byte("value3"), 0644), 1994 jc.ErrorIsNil) 1995 c.Assert( 1996 ioutil.WriteFile( 1997 filepath.Join(baseDir, "machine"), 1998 []byte("wibble"), 0644), 1999 jc.ErrorIsNil) 2000 2001 bundleData, err := charmrepo.ReadBundleFile(bundleFile) 2002 c.Assert(err, jc.ErrorIsNil) 2003 2004 err = processBundleIncludes(baseDir, bundleData) 2005 c.Assert(err, jc.ErrorIsNil) 2006 2007 django := bundleData.Applications["django"] 2008 c.Check(django.Annotations["key1"], gc.Equals, "value1") 2009 c.Check(django.Annotations["key2"], gc.Equals, "value2") 2010 c.Check(django.Annotations["key3"], gc.Equals, "value3") 2011 c.Check(django.Options["private"], gc.Equals, "KgwAFwg=") 2012 annotations := bundleData.Machines["1"].Annotations 2013 c.Check(annotations["foo"], gc.Equals, "bar") 2014 c.Check(annotations["baz"], gc.Equals, "wibble") 2015 } 2016 2017 type ProcessBundleOverlaySuite struct { 2018 coretesting.BaseSuite 2019 2020 bundleData *charm.BundleData 2021 } 2022 2023 var _ = gc.Suite(&ProcessBundleOverlaySuite{}) 2024 2025 func (s *ProcessBundleOverlaySuite) SetUpTest(c *gc.C) { 2026 s.BaseSuite.SetUpTest(c) 2027 2028 baseBundle := ` 2029 applications: 2030 django: 2031 expose: true 2032 charm: cs:django 2033 num_units: 1 2034 options: 2035 general: good 2036 annotations: 2037 key1: value1 2038 key2: value2 2039 to: [1] 2040 memcached: 2041 charm: xenial/mem-47 2042 num_units: 1 2043 options: 2044 key: value 2045 relations: 2046 - - "django" 2047 - "memcached" 2048 machines: 2049 1: 2050 annotations: {foo: bar}` 2051 2052 baseDir := c.MkDir() 2053 bundleFile := filepath.Join(baseDir, "bundle.yaml") 2054 err := ioutil.WriteFile(bundleFile, []byte(baseBundle), 0644) 2055 c.Assert(err, jc.ErrorIsNil) 2056 2057 s.bundleData, err = charmrepo.ReadBundleFile(bundleFile) 2058 c.Assert(err, jc.ErrorIsNil) 2059 } 2060 2061 func (s *ProcessBundleOverlaySuite) writeFile(c *gc.C, content string) string { 2062 // Write the content to a file in a new directoryt and return the file. 2063 baseDir := c.MkDir() 2064 filename := filepath.Join(baseDir, "config.yaml") 2065 err := ioutil.WriteFile(filename, []byte(content), 0644) 2066 c.Assert(err, jc.ErrorIsNil) 2067 return filename 2068 } 2069 2070 func (s *ProcessBundleOverlaySuite) TestNoFile(c *gc.C) { 2071 err := processBundleOverlay(s.bundleData) 2072 c.Assert(err, jc.ErrorIsNil) 2073 } 2074 2075 func (s *ProcessBundleOverlaySuite) TestBadFile(c *gc.C) { 2076 err := processBundleOverlay(s.bundleData, "bad") 2077 c.Assert(err, gc.ErrorMatches, `unable to read bundle overlay file ".*": bundle not found: .*bad`) 2078 } 2079 2080 func (s *ProcessBundleOverlaySuite) TestGoodYAML(c *gc.C) { 2081 filename := s.writeFile(c, "bad:\n\tindent") 2082 err := processBundleOverlay(s.bundleData, filename) 2083 c.Assert(err, gc.ErrorMatches, `unable to read bundle overlay file ".*": cannot unmarshal bundle data: yaml: line 2: found character that cannot start any token`) 2084 } 2085 2086 func (s *ProcessBundleOverlaySuite) TestReplaceZeroValues(c *gc.C) { 2087 config := ` 2088 applications: 2089 django: 2090 expose: false 2091 num_units: 0 2092 ` 2093 filename := s.writeFile(c, config) 2094 err := processBundleOverlay(s.bundleData, filename) 2095 c.Assert(err, jc.ErrorIsNil) 2096 django := s.bundleData.Applications["django"] 2097 2098 c.Check(django.Expose, jc.IsFalse) 2099 c.Check(django.NumUnits, gc.Equals, 0) 2100 } 2101 2102 func (s *ProcessBundleOverlaySuite) TestMachineReplacement(c *gc.C) { 2103 config := ` 2104 machines: 2105 1: 2106 2: 2107 ` 2108 filename := s.writeFile(c, config) 2109 err := processBundleOverlay(s.bundleData, filename) 2110 c.Assert(err, jc.ErrorIsNil) 2111 2112 var machines []string 2113 for key := range s.bundleData.Machines { 2114 machines = append(machines, key) 2115 } 2116 sort.Strings(machines) 2117 c.Assert(machines, jc.DeepEquals, []string{"1", "2"}) 2118 } 2119 2120 func (s *ProcessBundleOverlaySuite) assertApplications(c *gc.C, appNames ...string) { 2121 var applications []string 2122 for key := range s.bundleData.Applications { 2123 applications = append(applications, key) 2124 } 2125 sort.Strings(applications) 2126 sort.Strings(appNames) 2127 c.Assert(applications, jc.DeepEquals, appNames) 2128 } 2129 2130 func (s *ProcessBundleOverlaySuite) TestNewApplication(c *gc.C) { 2131 config := ` 2132 applications: 2133 postgresql: 2134 charm: cs:postgresql 2135 num_units: 1 2136 relations: 2137 - - "postgresql:pgsql" 2138 - "django:pgsql" 2139 ` 2140 filename := s.writeFile(c, config) 2141 err := processBundleOverlay(s.bundleData, filename) 2142 c.Assert(err, jc.ErrorIsNil) 2143 s.assertApplications(c, "django", "memcached", "postgresql") 2144 c.Assert(s.bundleData.Relations, jc.DeepEquals, [][]string{ 2145 {"django", "memcached"}, 2146 {"postgresql:pgsql", "django:pgsql"}, 2147 }) 2148 } 2149 2150 func (s *ProcessBundleOverlaySuite) TestRemoveApplication(c *gc.C) { 2151 config := ` 2152 applications: 2153 memcached: 2154 ` 2155 filename := s.writeFile(c, config) 2156 err := processBundleOverlay(s.bundleData, filename) 2157 c.Assert(err, jc.ErrorIsNil) 2158 s.assertApplications(c, "django") 2159 c.Assert(s.bundleData.Relations, gc.HasLen, 0) 2160 } 2161 2162 func (s *ProcessBundleOverlaySuite) TestRemoveUnknownApplication(c *gc.C) { 2163 config := ` 2164 applications: 2165 unknown: 2166 ` 2167 filename := s.writeFile(c, config) 2168 err := processBundleOverlay(s.bundleData, filename) 2169 c.Assert(err, jc.ErrorIsNil) 2170 s.assertApplications(c, "django", "memcached") 2171 c.Assert(s.bundleData.Relations, jc.DeepEquals, [][]string{ 2172 {"django", "memcached"}, 2173 }) 2174 } 2175 2176 func (s *ProcessBundleOverlaySuite) TestIncludes(c *gc.C) { 2177 config := ` 2178 applications: 2179 django: 2180 options: 2181 private: include-base64://sekrit.binary 2182 annotations: 2183 key3: include-file://annotation 2184 ` 2185 filename := s.writeFile(c, config) 2186 baseDir := filepath.Dir(filename) 2187 2188 c.Assert( 2189 ioutil.WriteFile( 2190 filepath.Join(baseDir, "sekrit.binary"), 2191 []byte{42, 12, 0, 23, 8}, 0644), 2192 jc.ErrorIsNil) 2193 c.Assert( 2194 ioutil.WriteFile( 2195 filepath.Join(baseDir, "annotation"), 2196 []byte("value3"), 0644), 2197 jc.ErrorIsNil) 2198 2199 err := processBundleOverlay(s.bundleData, filename) 2200 c.Assert(err, jc.ErrorIsNil) 2201 django := s.bundleData.Applications["django"] 2202 c.Check(django.Annotations, jc.DeepEquals, map[string]string{ 2203 "key1": "value1", 2204 "key2": "value2", 2205 "key3": "value3"}) 2206 c.Check(django.Options, jc.DeepEquals, map[string]interface{}{ 2207 "general": "good", 2208 "private": "KgwAFwg="}) 2209 } 2210 2211 func (s *ProcessBundleOverlaySuite) TestRemainingFields(c *gc.C) { 2212 // Note that we don't care about the actual values here 2213 // as bundle validation is done after replacement. 2214 config := ` 2215 applications: 2216 django: 2217 charm: cs:django-23 2218 series: wisty 2219 resources: 2220 something: or other 2221 to: 2222 - 3 2223 constraints: big machine 2224 storage: 2225 disk: big 2226 devices: 2227 gpu: 1,nvidia.com/gpu 2228 bindings: 2229 where: dmz 2230 ` 2231 filename := s.writeFile(c, config) 2232 err := processBundleOverlay(s.bundleData, filename) 2233 c.Assert(err, jc.ErrorIsNil) 2234 django := s.bundleData.Applications["django"] 2235 2236 c.Check(django.Charm, gc.Equals, "cs:django-23") 2237 c.Check(django.Series, gc.Equals, "wisty") 2238 c.Check(django.Resources, jc.DeepEquals, map[string]interface{}{ 2239 "something": "or other"}) 2240 c.Check(django.To, jc.DeepEquals, []string{"3"}) 2241 c.Check(django.Constraints, gc.Equals, "big machine") 2242 c.Check(django.Storage, jc.DeepEquals, map[string]string{ 2243 "disk": "big"}) 2244 c.Check(django.Devices, jc.DeepEquals, map[string]string{ 2245 "gpu": "1,nvidia.com/gpu"}) 2246 c.Check(django.EndpointBindings, jc.DeepEquals, map[string]string{ 2247 "where": "dmz"}) 2248 } 2249 2250 func (s *ProcessBundleOverlaySuite) TestYAMLInterpolation(c *gc.C) { 2251 2252 removeDjango := s.writeFile(c, ` 2253 applications: 2254 django: 2255 `) 2256 2257 addWiki := s.writeFile(c, ` 2258 defaultwiki: &DEFAULTWIKI 2259 charm: "cs:trusty/mediawiki-5" 2260 num_units: 1 2261 options: &WIKIOPTS 2262 debug: false 2263 name: Please set name of wiki 2264 skin: vector 2265 2266 applications: 2267 wiki: 2268 <<: *DEFAULTWIKI 2269 options: 2270 <<: *WIKIOPTS 2271 name: The name override 2272 relations: 2273 - - "wiki" 2274 - "memcached" 2275 `) 2276 2277 err := processBundleOverlay(s.bundleData, removeDjango, addWiki) 2278 c.Assert(err, jc.ErrorIsNil) 2279 2280 s.assertApplications(c, "memcached", "wiki") 2281 c.Assert(s.bundleData.Relations, jc.DeepEquals, [][]string{ 2282 {"wiki", "memcached"}, 2283 }) 2284 wiki := s.bundleData.Applications["wiki"] 2285 c.Assert(wiki.Charm, gc.Equals, "cs:trusty/mediawiki-5") 2286 c.Assert(wiki.Options, gc.DeepEquals, 2287 map[string]interface{}{ 2288 "debug": false, 2289 "name": "The name override", 2290 "skin": "vector", 2291 }) 2292 } 2293 2294 func (s *BundleDeployCharmStoreSuite) TestDeployBundlePassesSequences(c *gc.C) { 2295 testcharms.UploadCharm(c, s.client, "xenial/django-42", "dummy") 2296 2297 // Deploy another django app with two units, this will bump the sequences 2298 // for machines and the django application. Then remove them both. 2299 app := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 2300 Name: "django"}) 2301 u1 := s.Factory.MakeUnit(c, &factory.UnitParams{ 2302 Application: app, 2303 }) 2304 u2 := s.Factory.MakeUnit(c, &factory.UnitParams{ 2305 Application: app, 2306 }) 2307 var machines []*state.Machine 2308 var ids []string 2309 destroyUnitsMachine := func(u *state.Unit) { 2310 id, err := u.AssignedMachineId() 2311 c.Assert(err, jc.ErrorIsNil) 2312 ids = append(ids, id) 2313 m, err := s.State.Machine(id) 2314 c.Assert(err, jc.ErrorIsNil) 2315 machines = append(machines, m) 2316 c.Assert(m.ForceDestroy(), jc.ErrorIsNil) 2317 } 2318 // Tear them down. This is somewhat convoluted, but it is what we need 2319 // to do to properly cleanly tear down machines. 2320 c.Assert(app.Destroy(), jc.ErrorIsNil) 2321 destroyUnitsMachine(u1) 2322 destroyUnitsMachine(u2) 2323 c.Assert(s.State.Cleanup(), jc.ErrorIsNil) 2324 for _, m := range machines { 2325 c.Assert(m.EnsureDead(), jc.ErrorIsNil) 2326 c.Assert(m.MarkForRemoval(), jc.ErrorIsNil) 2327 } 2328 c.Assert(s.State.CompleteMachineRemovals(ids...), jc.ErrorIsNil) 2329 2330 apps, err := s.State.AllApplications() 2331 c.Assert(err, jc.ErrorIsNil) 2332 c.Assert(apps, gc.HasLen, 0) 2333 machines, err = s.State.AllMachines() 2334 c.Assert(err, jc.ErrorIsNil) 2335 c.Assert(machines, gc.HasLen, 0) 2336 2337 stdOut, _, err := s.DeployBundleYAMLWithOutput(c, ` 2338 applications: 2339 django: 2340 charm: cs:xenial/django-42 2341 num_units: 2 2342 `) 2343 c.Check(stdOut, gc.Equals, ""+ 2344 "Executing changes:\n"+ 2345 "- upload charm cs:xenial/django-42 for series xenial\n"+ 2346 "- deploy application django on xenial using cs:xenial/django-42\n"+ 2347 "- add unit django/2 to new machine 2\n"+ 2348 "- add unit django/3 to new machine 3", 2349 ) 2350 c.Assert(err, jc.ErrorIsNil) 2351 s.assertUnitsCreated(c, map[string]string{ 2352 "django/2": "2", 2353 "django/3": "3", 2354 }) 2355 } 2356 2357 type removeRelationsSuite struct{} 2358 2359 var ( 2360 _ = gc.Suite(&removeRelationsSuite{}) 2361 2362 sampleRelations = [][]string{ 2363 {"kubernetes-master:kube-control", "kubernetes-worker:kube-control"}, 2364 {"kubernetes-master:etcd", "etcd:db"}, 2365 {"kubernetes-worker:kube-api-endpoint", "kubeapi-load-balancer:website"}, 2366 {"flannel", "etcd"}, // removed :endpoint 2367 {"flannel:cni", "kubernetes-master:cni"}, 2368 {"flannel:cni", "kubernetes-worker:cni"}, 2369 } 2370 ) 2371 2372 func (*removeRelationsSuite) TestNil(c *gc.C) { 2373 result := removeRelations(nil, "foo") 2374 c.Assert(result, gc.HasLen, 0) 2375 } 2376 2377 func (*removeRelationsSuite) TestEmpty(c *gc.C) { 2378 result := removeRelations([][]string{}, "foo") 2379 c.Assert(result, gc.HasLen, 0) 2380 } 2381 2382 func (*removeRelationsSuite) TestAppNotThere(c *gc.C) { 2383 result := removeRelations(sampleRelations, "foo") 2384 c.Assert(result, jc.DeepEquals, sampleRelations) 2385 } 2386 2387 func (*removeRelationsSuite) TestAppBadRelationsKept(c *gc.C) { 2388 badRelations := [][]string{{"single value"}, {"three", "string", "values"}} 2389 result := removeRelations(badRelations, "foo") 2390 c.Assert(result, jc.DeepEquals, badRelations) 2391 } 2392 2393 func (*removeRelationsSuite) TestRemoveFromRight(c *gc.C) { 2394 result := removeRelations(sampleRelations, "etcd") 2395 c.Assert(result, jc.DeepEquals, [][]string{ 2396 {"kubernetes-master:kube-control", "kubernetes-worker:kube-control"}, 2397 {"kubernetes-worker:kube-api-endpoint", "kubeapi-load-balancer:website"}, 2398 {"flannel:cni", "kubernetes-master:cni"}, 2399 {"flannel:cni", "kubernetes-worker:cni"}, 2400 }) 2401 } 2402 2403 func (*removeRelationsSuite) TestRemoveFromLeft(c *gc.C) { 2404 result := removeRelations(sampleRelations, "flannel") 2405 c.Assert(result, jc.DeepEquals, [][]string{ 2406 {"kubernetes-master:kube-control", "kubernetes-worker:kube-control"}, 2407 {"kubernetes-master:etcd", "etcd:db"}, 2408 {"kubernetes-worker:kube-api-endpoint", "kubeapi-load-balancer:website"}, 2409 }) 2410 } 2411 2412 func missingFileRegex(filename string) string { 2413 text := "no such file or directory" 2414 if runtime.GOOS == "windows" { 2415 text = "The system cannot find the file specified." 2416 } 2417 return fmt.Sprintf("open .*%s: %s", filename, text) 2418 }