Merge lp:~hazmat/pyjuju/formula-upgrade-removes-obsolete into lp:pyjuju

Proposed by Kapil Thangavelu
Status: Work in progress
Proposed branch: lp:~hazmat/pyjuju/formula-upgrade-removes-obsolete
Merge into: lp:pyjuju
Diff against target: 295 lines (+141/-9)
9 files modified
ensemble/agents/tests/test_unit.py (+50/-4)
ensemble/agents/unit.py (+19/-3)
ensemble/formula/base.py (+7/-0)
ensemble/formula/bundle.py (+7/-0)
ensemble/formula/directory.py (+21/-1)
ensemble/formula/tests/test_base.py (+4/-0)
ensemble/formula/tests/test_bundle.py (+7/-0)
ensemble/formula/tests/test_directory.py (+17/-1)
ensemble/unit/tests/test_lifecycle.py (+9/-0)
To merge this branch: bzr merge lp:~hazmat/pyjuju/formula-upgrade-removes-obsolete
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Needs Information
Review via email: mp+66940@code.launchpad.net

Description of the change

On upgrade old formula files and directories not present in the new formula are removed. Also adds a dirent manifest for formulas. In retrospect this might have been achievable more simply with just removing the old formula dir while hook execution is stopped, and extracting the new one.. i had already implemented most of this before i realized that though..

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

[1]

25 + stale_file_path = os.path.join(formula_dir, "old-file")
26 + fh = open(stale_file_path, "w")
27 + fh.write("hello world")
28 + fh.close()

That's not a stale file, but a file created dynamically. I'm a bit concerned
about killing arbitrary files that the formula created, as it's basically saying
that the formula directory is necessarily read-only. Is that what we want?

review: Needs Information
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

I talked with gustavo about this, and we settled on an explicit manifest of install files to be used as a delta, to avoid killing any formula state files. alternatively we'd have to specify a state dir for formulas like 'var'.

Unmerged revisions

265. By Kapil Thangavelu

stale formula files are removed on formula upgrade.

264. By Kapil Thangavelu

dynamically computed manifests for formulas

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ensemble/agents/tests/test_unit.py'
--- ensemble/agents/tests/test_unit.py 2011-05-26 17:01:24 +0000
+++ ensemble/agents/tests/test_unit.py 2011-07-05 17:39:27 +0000
@@ -1,6 +1,7 @@
1import argparse1import argparse
2import logging2import logging
3import os3import os
4import stat
4import yaml5import yaml
56
6from twisted.internet.defer import (7from twisted.internet.defer import (
@@ -501,16 +502,51 @@
501 yield self.agent.workflow.fire_transition("stop")502 yield self.agent.workflow.fire_transition("stop")
502503
503 @inlineCallbacks504 @inlineCallbacks
505 def test_upgrade_removes_stale_files(self):
506 """A formula upgrade removes any old formula files."""
507 self.agent.set_watch_enabled(False)
508 yield self.agent.startService()
509 yield self.mark_formula_upgrade()
510
511 # Add a stale entries which should get removed.
512 formula_dir = os.path.join(self.agent.unit_directory, "formula")
513
514 stale_file_path = os.path.join(formula_dir, "old-file")
515 fh = open(stale_file_path, "w")
516 fh.write("hello world")
517 fh.close()
518
519 stale_hook_path = os.path.join(formula_dir, "hooks", "stop")
520 fh = open(stale_hook_path, "w")
521 fh.write("#!/bin/bash\necho done")
522 fh.close()
523
524 stale_dir = os.path.join(formula_dir, "empty")
525 os.mkdir(stale_dir)
526
527 stale = [stale_file_path, stale_hook_path, stale_dir]
528
529 # Perform the upgrade and verify
530 hook_done = self.wait_on_hook(
531 "upgrade-formula", executor=self.agent.executor)
532
533 upgrade = FormulaUpgradeOperation(self.agent)
534 value = yield upgrade.run()
535 self.assertTrue(value)
536 yield hook_done
537
538 for stale_path in stale:
539 self.assertFalse(os.path.exists(stale_path))
540
541 @inlineCallbacks
504 def test_agent_upgrade(self):542 def test_agent_upgrade(self):
505 """The agent can succesfully upgrade its formula."""543 """The agent can succesfully upgrade its formula."""
506 self.agent.set_watch_enabled(False)544 self.agent.set_watch_enabled(False)
507 yield self.agent.startService()545 yield self.agent.startService()
508
509 yield self.mark_formula_upgrade()546 yield self.mark_formula_upgrade()
510547
511 hook_done = self.wait_on_hook(548 hook_done = self.wait_on_hook(
512 "upgrade-formula", executor=self.agent.executor)549 "upgrade-formula", executor=self.agent.executor)
513 self.write_hook("upgrade-formula", "#!/bin/bash\nexit 0")
514 output = self.capture_logging("unit.upgrade", level=logging.DEBUG)550 output = self.capture_logging("unit.upgrade", level=logging.DEBUG)
515551
516 # Do the upgrade552 # Do the upgrade
@@ -589,8 +625,19 @@
589 self.agent.set_watch_enabled(False)625 self.agent.set_watch_enabled(False)
590 yield self.agent.startService()626 yield self.agent.startService()
591627
592 # Upload a new version of the unit's formula628 # Upload a new version of the unit's formula, with a bad
629 # upgrade hook.
593 repository = self.increment_formula(self.formula)630 repository = self.increment_formula(self.formula)
631 formula_dir = repository.find("mysql").path
632 os.mkdir(os.path.join(formula_dir, "hooks"))
633 upgrade_hook_path = os.path.join(
634 formula_dir, "hooks", "upgrade-formula")
635
636 fp = open(upgrade_hook_path, "w")
637 fp.write("#!/bin/bash\nexit 1")
638 fp.close()
639 os.chmod(upgrade_hook_path, stat.S_IRWXU)
640
594 formula, formula_state = yield self.publish_formula(641 formula, formula_state = yield self.publish_formula(
595 repository.find("mysql").path)642 repository.find("mysql").path)
596643
@@ -600,7 +647,6 @@
600647
601 hook_done = self.wait_on_hook(648 hook_done = self.wait_on_hook(
602 "upgrade-formula", executor=self.agent.executor)649 "upgrade-formula", executor=self.agent.executor)
603 self.write_hook("upgrade-formula", "#!/bin/bash\nexit 1")
604 output = self.capture_logging("unit.upgrade", level=logging.DEBUG)650 output = self.capture_logging("unit.upgrade", level=logging.DEBUG)
605651
606 # Do the upgrade652 # Do the upgrade
607653
=== modified file 'ensemble/agents/unit.py'
--- ensemble/agents/unit.py 2011-05-24 18:07:03 +0000
+++ ensemble/agents/unit.py 2011-07-05 17:39:27 +0000
@@ -6,6 +6,7 @@
6from twisted.internet.defer import inlineCallbacks, returnValue6from twisted.internet.defer import inlineCallbacks, returnValue
77
8from ensemble.errors import EnsembleError8from ensemble.errors import EnsembleError
9from ensemble.formula import get_formula_from_path
9from ensemble.state.service import ServiceStateManager10from ensemble.state.service import ServiceStateManager
10from ensemble.hooks.protocol import UnitSettingsFactory11from ensemble.hooks.protocol import UnitSettingsFactory
11from ensemble.hooks.executor import HookExecutor12from ensemble.hooks.executor import HookExecutor
@@ -195,6 +196,21 @@
195 shutil.rmtree(self._formula_directory)196 shutil.rmtree(self._formula_directory)
196 return result197 return result
197198
199 def _remove_stale(self, new_formula, formula_dir):
200 new_entries = set(new_formula.manifest())
201 old_entries = set(formula_dir.manifest())
202
203 stale_entries = list(old_entries - new_entries)
204 stale_entries.sort(lambda x, y: cmp(len(x), len(y)))
205 stale_entries.reverse()
206
207 for entry in stale_entries:
208 entry_path = os.path.join(formula_dir.path, entry)
209 if entry_path.endswith("/"):
210 os.rmdir(entry_path)
211 else:
212 os.remove(entry_path)
213
198 def run(self):214 def run(self):
199 d = self._run()215 d = self._run()
200 d.addBoth(self._remove_tree)216 d.addBoth(self._remove_tree)
@@ -254,13 +270,13 @@
254 yield self._agent.unit_state.set_formula_id(service_formula_id)270 yield self._agent.unit_state.set_formula_id(service_formula_id)
255271
256 # Extract formula272 # Extract formula
273 formula_dir = os.path.join(self._agent.unit_directory, "formula")
257 self._log.debug("Extracting new formula.")274 self._log.debug("Extracting new formula.")
258 formula.extract_to(275 self._remove_stale(formula, get_formula_from_path(formula_dir))
259 os.path.join(self._agent.unit_directory, "formula"))276 formula.extract_to(formula_dir)
260277
261 # Upgrade278 # Upgrade
262 self._log.debug("Invoking upgrade transition.")279 self._log.debug("Invoking upgrade transition.")
263
264 success = yield self._agent.workflow.fire_transition(280 success = yield self._agent.workflow.fire_transition(
265 "upgrade_formula")281 "upgrade_formula")
266282
267283
=== modified file 'ensemble/formula/base.py'
--- ensemble/formula/base.py 2010-08-18 15:23:10 +0000
+++ ensemble/formula/base.py 2011-07-05 17:39:27 +0000
@@ -33,3 +33,10 @@
33 if self._sha256 is None:33 if self._sha256 is None:
34 self._sha256 = self.compute_sha256()34 self._sha256 = self.compute_sha256()
35 return self._sha25635 return self._sha256
36
37 def manifest(self):
38 """Return a file and directory manifest as a list of entries.
39
40 The entries are relative to the formula.
41 """
42 self._unsupported("manifest()")
3643
=== modified file 'ensemble/formula/bundle.py'
--- ensemble/formula/bundle.py 2011-06-05 20:19:18 +0000
+++ ensemble/formula/bundle.py 2011-07-05 17:39:27 +0000
@@ -64,3 +64,10 @@
64 path"""64 path"""
65 dn = tempfile.mkdtemp(prefix="tmp")65 dn = tempfile.mkdtemp(prefix="tmp")
66 return self.extract_to(dn)66 return self.extract_to(dn)
67
68 def manifest(self):
69 """Return a file and directory manifest as a list of entries.
70
71 The entries are relative to the formula.
72 """
73 return ZipFile(self.path, "r").namelist()
6774
=== modified file 'ensemble/formula/directory.py'
--- ensemble/formula/directory.py 2011-06-09 07:10:07 +0000
+++ ensemble/formula/directory.py 2011-07-05 17:39:27 +0000
@@ -1,6 +1,7 @@
1import itertools
1import os2import os
3import tempfile
2import zipfile4import zipfile
3import tempfile
45
5from ensemble.formula.config import ConfigOptions6from ensemble.formula.config import ConfigOptions
6from ensemble.formula.metadata import MetaData7from ensemble.formula.metadata import MetaData
@@ -77,3 +78,22 @@
77 Compute sha256, based on the bundle.78 Compute sha256, based on the bundle.
78 """79 """
79 return self.as_bundle().compute_sha256()80 return self.as_bundle().compute_sha256()
81
82 def manifest(self):
83 """Return a file and directory manifest as a list of entries.
84
85 The entries are relative to the formula.
86 """
87 entries = []
88 for dirpath, dirnames, filenames in os.walk(self.path):
89 relative_path = dirpath[len(self.path):]
90 dirnames = ["%s/" % d for d in dirnames]
91 for dirent in itertools.chain(dirnames, filenames):
92 entry = "/".join((relative_path, dirent))
93 if entry.startswith("/"):
94 entry = entry[1:]
95 if self._ignore(entry):
96 continue
97 entries.append(entry)
98
99 return entries
80100
=== modified file 'ensemble/formula/tests/test_base.py'
--- ensemble/formula/tests/test_base.py 2010-09-09 18:34:47 +0000
+++ ensemble/formula/tests/test_base.py 2011-07-05 17:39:27 +0000
@@ -28,6 +28,10 @@
28 self.assertUnsupported(lambda: self.formula.compute_sha256(),28 self.assertUnsupported(lambda: self.formula.compute_sha256(),
29 "compute_sha256()")29 "compute_sha256()")
3030
31 def test_manifest_is_unsupported(self):
32 self.assertUnsupported(lambda: self.formula.manifest(),
33 "manifest()")
34
31 def test_get_sha256_fails_if_not_preset(self):35 def test_get_sha256_fails_if_not_preset(self):
32 """36 """
33 get_sha256() will internally call compute_sha256() if37 get_sha256() will internally call compute_sha256() if
3438
=== modified file 'ensemble/formula/tests/test_bundle.py'
--- ensemble/formula/tests/test_bundle.py 2011-05-06 04:45:17 +0000
+++ ensemble/formula/tests/test_bundle.py 2011-07-05 17:39:27 +0000
@@ -131,3 +131,10 @@
131 fn = os.path.split(f2.path)[1]131 fn = os.path.split(f2.path)[1]
132 # verify that it used the expected prefix132 # verify that it used the expected prefix
133 self.assertStartsWith(fn, "tmp")133 self.assertStartsWith(fn, "tmp")
134
135 def test_manifest(self):
136 """A formula manifest contains all directories and files contained."""
137 bundle = FormulaBundle(self.filename)
138 self.assertEqual(bundle.manifest(),
139 ["config.yaml", "metadata.yaml", "src/",
140 "src/hello.c", "empty/"])
134141
=== modified file 'ensemble/formula/tests/test_directory.py'
--- ensemble/formula/tests/test_directory.py 2011-05-06 04:45:17 +0000
+++ ensemble/formula/tests/test_directory.py 2011-07-05 17:39:27 +0000
@@ -56,7 +56,11 @@
56 included = [info.filename for info in zf.infolist()]56 included = [info.filename for info in zf.infolist()]
57 self.assertEqual(57 self.assertEqual(
58 set(included),58 set(included),
59 set(("metadata.yaml", "empty/", "src/", "src/hello.c", "config.yaml")))59 set(("metadata.yaml",
60 "empty/",
61 "src/",
62 "src/hello.c",
63 "config.yaml")))
6064
61 def test_as_bundle(self):65 def test_as_bundle(self):
62 directory = FormulaDirectory(self.sample_dir1)66 directory = FormulaDirectory(self.sample_dir1)
@@ -131,3 +135,15 @@
131 self.assertEquals(directory.config.get_serialization_data(),135 self.assertEquals(directory.config.get_serialization_data(),
132 sample_yaml_data)136 sample_yaml_data)
133137
138 def test_manifest(self):
139 """A formula manifest contains all directories and files contained."""
140 directory = FormulaDirectory(self.sample_dir1)
141 self.assertEqual(directory.manifest(), ["metadata.yaml"])
142 directory = FormulaDirectory(sample_directory)
143 self.assertEqual(
144 set(directory.manifest()),
145 set(("metadata.yaml",
146 "empty/",
147 "src/",
148 "src/hello.c",
149 "config.yaml")))
134150
=== modified file 'ensemble/unit/tests/test_lifecycle.py'
--- ensemble/unit/tests/test_lifecycle.py 2011-05-26 11:20:43 +0000
+++ ensemble/unit/tests/test_lifecycle.py 2011-07-05 17:39:27 +0000
@@ -62,7 +62,16 @@
62 self.unit_directory = os.path.join(self.ensemble_directory,62 self.unit_directory = os.path.join(self.ensemble_directory,
63 "units",63 "units",
64 self.states["unit"].unit_name.replace("/", "-"))64 self.states["unit"].unit_name.replace("/", "-"))
65
65 os.makedirs(os.path.join(self.unit_directory, "formula", "hooks"))66 os.makedirs(os.path.join(self.unit_directory, "formula", "hooks"))
67 fp = open(os.path.join(
68 self.unit_directory, "formula", "metadata.yaml"), "w")
69 fp.write(yaml.dump(
70 {"ensemble": "formula",
71 "revision": 1,
72 "name": "mysql",
73 "description": "db",
74 "summary": "db"}))
66 os.makedirs(os.path.join(self.ensemble_directory, "state"))75 os.makedirs(os.path.join(self.ensemble_directory, "state"))
6776
68 def write_hook(self, name, text, no_exec=False):77 def write_hook(self, name, text, no_exec=False):

Subscribers

People subscribed via source and target branches

to status/vote changes: