3
from twisted.internet.defer import inlineCallbacks
4
3
from yaml import dump
6
from ensemble.control import main
7
from ensemble.errors import FileNotFound
8
from ensemble.environment.environment import Environment
9
from ensemble.formula.repository import LocalFormulaRepository
5
from twisted.internet.defer import inlineCallbacks, succeed
11
from ensemble.unit.workflow import UnitWorkflowState
7
from juju.charm.directory import CharmDirectory
8
from juju.charm.repository import LocalCharmRepository
9
from juju.charm.tests.test_metadata import test_repository_path
10
from juju.charm.url import CharmURL
11
from juju.control import main
12
from juju.errors import FileNotFound
13
from juju.environment.environment import Environment
14
from juju.unit.workflow import UnitWorkflowState
13
16
from .common import MachineControlToolTest
16
class FormulaUpgradeTestBase(object):
19
class CharmUpgradeTestBase(object):
18
def add_formula(self, metadata, repository_dir=None):
22
self, metadata, revision, repository_dir=None, bundle=False,
25
Helper method to create a charm in the given repo.
19
27
if repository_dir is None:
20
28
repository_dir = self.makeDir()
21
formula_dir = self.makeDir(dirname=repository_dir)
22
fh = open(os.path.join(formula_dir, "metadata.yaml"), "w")
23
fh.write(dump(metadata))
25
return LocalFormulaRepository(repository_dir)
29
series_dir = os.path.join(repository_dir, "series")
31
charm_dir = self.makeDir()
32
with open(os.path.join(charm_dir, "metadata.yaml"), "w") as f:
33
f.write(dump(metadata))
34
with open(os.path.join(charm_dir, "revision"), "w") as f:
35
f.write(str(revision))
37
with open(os.path.join(charm_dir, "config.yaml"), "w") as f:
40
CharmDirectory(charm_dir).make_archive(
41
os.path.join(series_dir, "%s.charm" % metadata["name"]))
43
os.rename(charm_dir, os.path.join(series_dir, metadata["name"]))
44
return LocalCharmRepository(repository_dir)
27
def increment_formula(self, formula):
28
metadata = formula.metadata.get_serialization_data()
46
def increment_charm(self, charm):
47
metadata = charm.metadata.get_serialization_data()
29
48
metadata["name"] = "mysql"
30
metadata["revision"] = 2
31
repository = self.add_formula(metadata)
49
repository = self.add_charm(metadata, charm.get_revision() + 1)
35
class ControlFormulaUpgradeTest(
36
MachineControlToolTest, FormulaUpgradeTestBase):
53
class ControlCharmUpgradeTest(
54
MachineControlToolTest, CharmUpgradeTestBase):
40
yield super(ControlFormulaUpgradeTest, self).setUp()
58
yield super(ControlCharmUpgradeTest, self).setUp()
42
"ensemble": "environments",
43
60
"environments": {"firstenv": {"type": "dummy"}}}
45
62
self.write_config(dump(config))
47
self.service_state1 = yield self.add_service_from_formula("mysql")
64
self.service_state1 = yield self.add_service_from_charm("mysql")
48
65
self.service_unit1 = yield self.service_state1.add_unit_state()
50
67
self.unit1_workflow = UnitWorkflowState(
72
89
finished = self.setup_cli_reactor()
74
91
self.mocker.replay()
75
main(["upgrade-formula", "--repository", repository.path, "mysql"])
92
main(["upgrade-charm", "--repository", repository.path, "mysql"])
78
# Verify the service has a new formula reference
79
formula_id = yield self.service_state1.get_formula_id()
80
self.assertEqual(formula_id, "local:mysql-2")
95
# Verify the service has a new charm reference
96
charm_id = yield self.service_state1.get_charm_id()
97
self.assertEqual(charm_id, "local:series/mysql-2")
82
99
# Verify the provider storage has been updated
83
formula = repository.find("mysql")
100
charm = yield repository.find(CharmURL.parse("local:series/mysql"))
84
101
storage = self.provider.get_file_storage()
86
103
yield storage.get(
87
"formulas/local:mysql-2:%s" % formula.get_sha256())
104
"local_3a_series_2f_mysql-2_3a_%s" % charm.get_sha256())
88
105
except FileNotFound:
89
self.fail("New formula not uploaded")
106
self.fail("New charm not uploaded")
91
108
# Verify the upgrade flag on the service units.
92
109
upgrade_flag = yield self.service_unit1.get_upgrade_flag()
93
110
self.assertTrue(upgrade_flag)
96
def test_upgrade_formula_with_unupgradeable_units(self):
113
def test_missing_repository(self):
114
finished = self.setup_cli_reactor()
118
main(["upgrade-charm", "mysql"])
121
self.assertIn("No repository specified", self.output.getvalue())
124
def test_upgrade_charm_with_unupgradeable_units(self):
97
125
"""If there are units that won't be upgraded, they will be reported,
98
126
other units will be upgraded.
100
repository = self.increment_formula(self.formula)
128
repository = self.increment_charm(self.charm)
101
129
service_unit2 = yield self.service_state1.add_unit_state()
103
131
finished = self.setup_cli_reactor()
104
132
self.setup_exit(0)
105
133
self.mocker.replay()
107
main(["upgrade-formula", "--repository", repository.path, "mysql"])
135
main(["upgrade-charm", "--repository", repository.path, "mysql"])
110
138
# Verify report of unupgradeable units
120
148
self.assertTrue(value)
123
def test_upgrade_formula_unknown_service(self):
151
def test_upgrade_charm_unknown_service(self):
124
152
finished = self.setup_cli_reactor()
125
153
self.setup_exit(0)
126
154
self.mocker.replay()
127
main(["upgrade-formula", "--repository", self.makeDir(), "volcano"])
155
main(["upgrade-charm", "--repository", self.makeDir(), "volcano"])
130
158
"Service 'volcano' was not found", self.stderr.getvalue())
133
def test_upgrade_formula_unknown_formula(self):
134
"""If a formula is not found in the repository, an error is given.
136
finished = self.setup_cli_reactor()
139
repository_dir = self.makeDir()
140
main(["upgrade-formula", "--repository", repository_dir, "mysql"])
143
"Formula 'mysql' not found in repository", self.output.getvalue())
146
def test_upgrade_formula_dryrun(self):
147
"""If a formula is not found in the repository, an error is given.
149
finished = self.setup_cli_reactor()
152
repository_dir = self.makeDir()
153
main(["upgrade-formula", "--repository", repository_dir, "mysql"])
156
"Formula 'mysql' not found in repository", self.output.getvalue())
159
def test_upgrade_formula_dryrun_reports_unupgradeable_units(self):
161
def test_upgrade_charm_unknown_charm(self):
162
"""If a charm is not found in the repository, an error is given.
164
finished = self.setup_cli_reactor()
167
repository_dir = self.makeDir()
168
os.mkdir(os.path.join(repository_dir, "series"))
169
main(["upgrade-charm", "--repository", repository_dir, "mysql"])
172
"Charm 'local:series/mysql' not found in repository",
173
self.output.getvalue())
176
def test_upgrade_charm_unknown_charm_dryrun(self):
177
"""If a charm is not found in the repository, an error is given.
179
finished = self.setup_cli_reactor()
182
repository_dir = self.makeDir()
183
os.mkdir(os.path.join(repository_dir, "series"))
184
main(["upgrade-charm", "--repository",
185
repository_dir, "mysql", "--dry-run"])
188
"Charm 'local:series/mysql' not found in repository",
189
self.output.getvalue())
192
def test_upgrade_charm_dryrun_reports_unupgradeable_units(self):
160
193
"""If there are units that won't be upgraded, dry-run will report them.
162
repository = self.increment_formula(self.formula)
195
repository = self.increment_charm(self.charm)
163
196
service_unit2 = yield self.service_state1.add_unit_state()
165
198
finished = self.setup_cli_reactor()
166
199
self.setup_exit(0)
167
200
self.mocker.replay()
169
main(["upgrade-formula", "-n",
202
main(["upgrade-charm", "-n",
170
203
"--repository", repository.path, "mysql"])
175
"Service would be upgraded from formula", self.output.getvalue())
208
"Service would be upgraded from charm", self.output.getvalue())
176
209
# Verify report of unupgradeable units
178
211
("Unit 'mysql/1' is not in a running state "
186
219
self.assertFalse(value)
189
def test_upgrade_formula_dryrun_latest(self):
190
"""Dry run mode outputs if the latest formula is deployed.
192
finished = self.setup_cli_reactor()
196
metadata = self.formula.metadata.get_serialization_data()
197
metadata["name"] = "mysql"
198
repository = self.add_formula(metadata)
199
main(["upgrade-formula", "--dry-run",
200
"--repository", repository.path, "mysql"])
203
"Service already running latest formula",
204
self.output.getvalue())
206
upgrade_flag = yield self.service_unit1.get_upgrade_flag()
207
self.assertFalse(upgrade_flag)
210
def test_upgrade_formula_service_using_latest(self):
211
"""If the service is already running the latest formula no work is done
213
An appropriate output noting this condition is given.
215
finished = self.setup_cli_reactor()
219
metadata = self.formula.metadata.get_serialization_data()
220
metadata["name"] = "mysql"
221
repository = self.add_formula(metadata)
222
main(["upgrade-formula", "--repository", repository.path, "mysql"])
226
"Formula 'namespace:mysql-1' is the latest revision known",
222
def test_apply_new_charm_defaults(self):
223
finished = self.setup_cli_reactor()
227
# Add a charm and its service.
228
metadata = {"name": "haiku",
229
"summary": "its short",
230
"description": "but with cadence"}
231
repository = self.add_charm(
236
"foo": {"type": "string",
237
"default": "foo-default",
238
"description": "Foo"},
239
"bar": {"type": "string",
240
"default": "bar-default",
241
"description": "Bar"},
245
charm_dir = yield repository.find(CharmURL.parse("local:series/haiku"))
246
service_state = yield self.add_service_from_charm(
247
"haiku", charm_dir=charm_dir)
249
# Update a config value
250
config = yield service_state.get_config()
251
config["foo"] = "abc"
255
repository = self.add_charm(
260
"foo": {"type": "string",
261
"default": "foo-default",
262
"description": "Foo"},
263
"bar": {"type": "string",
264
"default": "bar-default",
265
"description": "Bar"},
266
"dca": {"type": "string",
267
"default": "default-dca",
268
"description": "Airport"},
272
main(["upgrade-charm", "--repository", repository.path, "haiku"])
276
config = yield service_state.get_config()
279
{"foo": "abc", "dca": "default-dca", "bar": "bar-default"})
282
def test_latest_local_dry_run(self):
283
"""Do nothing; log that local charm would be re-revisioned and used"""
284
finished = self.setup_cli_reactor()
288
metadata = self.charm.metadata.get_serialization_data()
289
metadata["name"] = "mysql"
290
repository = self.add_charm(metadata, 1)
291
main(["upgrade-charm", "--dry-run",
292
"--repository", repository.path, "mysql"])
294
charm_path = os.path.join(repository.path, "series", "mysql")
296
"%s would be set to revision 2" % charm_path,
297
self.output.getvalue())
299
"Service would be upgraded from charm 'local:series/mysql-1' to "
300
"'local:series/mysql-2'",
301
self.output.getvalue())
303
with open(os.path.join(charm_path, "revision")) as f:
304
self.assertEquals(f.read(), "1")
306
upgrade_flag = yield self.service_unit1.get_upgrade_flag()
307
self.assertFalse(upgrade_flag)
310
def test_latest_local_live_fire(self):
311
"""Local charm should be re-revisioned and used; log that it was"""
312
finished = self.setup_cli_reactor()
316
metadata = self.charm.metadata.get_serialization_data()
317
metadata["name"] = "mysql"
318
repository = self.add_charm(metadata, 1)
319
main(["upgrade-charm", "--repository", repository.path, "mysql"])
322
charm_path = os.path.join(repository.path, "series", "mysql")
324
"Setting %s to revision 2" % charm_path,
325
self.output.getvalue())
327
with open(os.path.join(charm_path, "revision")) as f:
328
self.assertEquals(f.read(), "2\n")
330
upgrade_flag = yield self.service_unit1.get_upgrade_flag()
331
self.assertTrue(upgrade_flag)
334
def test_latest_local_leapfrog_dry_run(self):
335
"""Do nothing; log that local charm would be re-revisioned and used"""
336
finished = self.setup_cli_reactor()
340
metadata = self.charm.metadata.get_serialization_data()
341
metadata["name"] = "mysql"
342
repository = self.add_charm(metadata, 0)
343
main(["upgrade-charm", "--dry-run",
344
"--repository", repository.path, "mysql"])
346
charm_path = os.path.join(repository.path, "series", "mysql")
348
"%s would be set to revision 2" % charm_path,
349
self.output.getvalue())
351
"Service would be upgraded from charm 'local:series/mysql-1' to "
352
"'local:series/mysql-2'",
353
self.output.getvalue())
355
with open(os.path.join(charm_path, "revision")) as f:
356
self.assertEquals(f.read(), "0")
358
upgrade_flag = yield self.service_unit1.get_upgrade_flag()
359
self.assertFalse(upgrade_flag)
362
def test_latest_local_leapfrog_live_fire(self):
363
"""Local charm should be re-revisioned and used; log that it was"""
364
finished = self.setup_cli_reactor()
368
metadata = self.charm.metadata.get_serialization_data()
369
metadata["name"] = "mysql"
370
repository = self.add_charm(metadata, 0)
371
main(["upgrade-charm", "--repository", repository.path, "mysql"])
374
charm_path = os.path.join(repository.path, "series", "mysql")
376
"Setting %s to revision 2" % charm_path,
377
self.output.getvalue())
379
with open(os.path.join(charm_path, "revision")) as f:
380
self.assertEquals(f.read(), "2\n")
382
upgrade_flag = yield self.service_unit1.get_upgrade_flag()
383
self.assertTrue(upgrade_flag)
386
def test_latest_local_bundle_dry_run(self):
387
"""Do nothing; log that nothing would be done"""
388
finished = self.setup_cli_reactor()
392
metadata = self.charm.metadata.get_serialization_data()
393
metadata["name"] = "mysql"
394
repository = self.add_charm(metadata, 1, bundle=True)
395
main(["upgrade-charm", "--dry-run",
396
"--repository", repository.path, "mysql"])
399
"Service already running latest charm",
400
self.output.getvalue())
402
upgrade_flag = yield self.service_unit1.get_upgrade_flag()
403
self.assertFalse(upgrade_flag)
406
def test_latest_local_bundle_live_fire(self):
407
"""Do nothing; log that nothing was done"""
408
finished = self.setup_cli_reactor()
412
metadata = self.charm.metadata.get_serialization_data()
413
metadata["name"] = "mysql"
414
repository = self.add_charm(metadata, 1, bundle=True)
415
main(["upgrade-charm", "--repository", repository.path, "mysql"])
419
"Charm 'local:series/mysql-1' is the latest revision known",
420
self.output.getvalue())
422
upgrade_flag = yield self.service_unit1.get_upgrade_flag()
423
self.assertFalse(upgrade_flag)
426
class RemoteUpgradeCharmTest(MachineControlToolTest):
430
yield super(RemoteUpgradeCharmTest, self).setUp()
432
"environments": {"firstenv": {"type": "dummy"}}}
434
self.write_config(dump(config))
437
charm = CharmDirectory(os.path.join(
438
test_repository_path, "series", "mysql"))
439
self.charm_state_manager.add_charm_state(
440
"cs:series/mysql-1", charm, "")
441
self.service_state1 = yield self.add_service_from_charm(
442
"mysql", "cs:series/mysql-1")
443
self.service_unit1 = yield self.service_state1.add_unit_state()
445
self.unit1_workflow = UnitWorkflowState(
446
self.client, self.service_unit1, None, self.makeDir())
447
yield self.unit1_workflow.set_state("started")
449
self.environment = self.config.get_default()
450
self.provider = self.environment.get_machine_provider()
452
self.output = self.capture_logging()
453
self.stderr = self.capture_stream("stderr")
456
def test_latest_dry_run(self):
457
"""Do nothing; log that nothing would be done"""
458
finished = self.setup_cli_reactor()
460
getPage = self.mocker.replace("twisted.web.client.getPage")
462
"https://store.juju.ubuntu.com/"
463
"charm-info?charms=cs%3Aseries/mysql")
464
self.mocker.result(succeed(json.dumps(
465
{"cs:series/mysql": {"revision": 1, "sha256": "whatever"}})))
468
main(["upgrade-charm", "--dry-run", "mysql"])
471
"Service already running latest charm",
472
self.output.getvalue())
474
upgrade_flag = yield self.service_unit1.get_upgrade_flag()
475
self.assertFalse(upgrade_flag)
478
def test_latest_live_fire(self):
479
"""Do nothing; log that nothing was done"""
480
finished = self.setup_cli_reactor()
483
getPage = self.mocker.replace("twisted.web.client.getPage")
485
"https://store.juju.ubuntu.com/charm-info?charms=cs%3Aseries/mysql")
486
self.mocker.result(succeed(json.dumps(
487
{"cs:series/mysql": {"revision": 1, "sha256": "whatever"}})))
490
main(["upgrade-charm", "mysql"])
494
"Charm 'cs:series/mysql-1' is the latest revision known",
227
495
self.output.getvalue())
229
497
upgrade_flag = yield self.service_unit1.get_upgrade_flag()