Merge lp:~free.ekanayaka/landscape-client/auto-approve into lp:~landscape/landscape-client/trunk

Proposed by Free Ekanayaka
Status: Merged
Merge reported by: Free Ekanayaka
Merged at revision: not available
Proposed branch: lp:~free.ekanayaka/landscape-client/auto-approve
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 285 lines (+159/-30)
3 files modified
landscape/__init__.py (+1/-0)
landscape/package/changer.py (+58/-27)
landscape/package/tests/test_changer.py (+100/-3)
To merge this branch: bzr merge lp:~free.ekanayaka/landscape-client/auto-approve
Reviewer Review Type Date Requested Status
Thomas Herve (community) Approve
Kapil Thangavelu (community) Approve
Review via email: mp+18850@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

Nice branch, looks good +1

Some comments,

1) Spelling typo

204 + def test_perform_changes_with_allow_install_policy_and_removals(self):
205 + """
206 + The C{POLICY_ALLOW_INSTALLS} policy doens't allow additional packages

2) Pep8 (unrelated to branch but in a modified file), test_changer.py, line 721, is trailing whitespace.

3) This might be stylistic, but i find side effects in conditionals to be a bit hard to parse, although the test coverage is fine.

review: Approve
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Thanks Kapil, all points were addressed.

211. By Free Ekanayaka

Remove conditional with side-effects (Kapil [3])

212. By Free Ekanayaka

Fix typo and pep8 (Kapil [1], [2])

Revision history for this message
Thomas Herve (therve) wrote :

[1]
+ @param reset: iF C{True} all existing marks will be reset.

Typo: if

[2] For the sake of completeness, I would a POLICY_ALLOW_ALL_CHANGES, even if we don't expose it the new UI.

+1!

review: Approve
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Thanks Thomas! [1] and [2] fixed.

213. By Free Ekanayaka

Fix typo and add POLICY_ALLOW_ALL_CHANGES for allowing both installs and removals (Thomas [1], [2])

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'landscape/__init__.py'
2--- landscape/__init__.py 2010-02-01 09:09:54 +0000
3+++ landscape/__init__.py 2010-02-08 21:50:24 +0000
4@@ -21,6 +21,7 @@
5 #
6 # 3.3:
7 # * Add "binaries" field to "change-packages"
8+# * Add "policy" field to "change-packages"
9 # * Add new "change-package-locks" client accepted message type.
10 #
11 CLIENT_API = "3.3"
12
13=== modified file 'landscape/package/changer.py'
14--- landscape/package/changer.py 2010-02-08 08:22:09 +0000
15+++ landscape/package/changer.py 2010-02-08 21:50:24 +0000
16@@ -19,7 +19,8 @@
17 SUCCESS_RESULT = 1
18 ERROR_RESULT = 100
19 DEPENDENCY_ERROR_RESULT = 101
20-
21+POLICY_STRICT = 0
22+POLICY_ALLOW_INSTALLS = 1
23
24 # The amount of time to wait while we have unknown package data before
25 # reporting an error to the server in response to an operation.
26@@ -139,7 +140,7 @@
27 else:
28 raise PackageTaskError()
29
30- def init_channels(self, binaries):
31+ def init_channels(self, binaries=()):
32 """Initialize the Smart channels as needed.
33
34 @param binaries: A possibly empty list of 3-tuples of the form
35@@ -163,14 +164,16 @@
36
37 self._facade.ensure_channels_reloaded()
38
39- def mark_packages(self, upgrade=False, install=(), remove=()):
40+ def mark_packages(self, upgrade=False, install=(), remove=(), reset=True):
41 """Mark packages for upgrade, installation or removal.
42
43 @param upgrade: If C{True} mark all installed packages for upgrade.
44 @param install: A list of package ids to be marked for installation.
45 @param remove: A list of package ids to be marked for removal.
46+ @param reset: iF C{True} all existing marks will be reset.
47 """
48- self._facade.reset_marks()
49+ if reset:
50+ self._facade.reset_marks()
51
52 if upgrade:
53 for package in self._facade.get_packages():
54@@ -188,9 +191,12 @@
55 raise UnknownPackageData(hash)
56 mark_func(package)
57
58- def change_packages(self):
59+ def change_packages(self, policy):
60 """Perform the requested changes.
61
62+ @param policy: A value indicating what to do in case additional changes
63+ beside the ones explicitly requested are needed in order to fulfill
64+ the request (see L{complement_changes}).
65 @return: A L{ChangePackagesResult} holding the details about the
66 outcome of the requested changes.
67 """
68@@ -200,31 +206,56 @@
69 DependencyError, TransactionError, SmartError)
70
71 result = ChangePackagesResult()
72- try:
73- result.text = self._facade.perform_changes()
74- except (TransactionError, SmartError), exception:
75- result.code = ERROR_RESULT
76- result.text = exception.args[0]
77- except DependencyError, exception:
78- result.code = DEPENDENCY_ERROR_RESULT
79- for package in exception.packages:
80- hash = self._facade.get_package_hash(package)
81- id = self._store.get_hash_id(hash)
82- if id is None:
83- # Will have to wait until the server lets us know about
84- # this id.
85- raise UnknownPackageData(hash)
86- if package.installed:
87- # Package currently installed. Must remove it.
88- result.removals.append(id)
89+ count = 0
90+ while result.code is None:
91+ count += 1
92+ try:
93+ result.text = self._facade.perform_changes()
94+ except (TransactionError, SmartError), exception:
95+ result.code = ERROR_RESULT
96+ result.text = exception.args[0]
97+ except DependencyError, exception:
98+ for package in exception.packages:
99+ hash = self._facade.get_package_hash(package)
100+ id = self._store.get_hash_id(hash)
101+ if id is None:
102+ # Will have to wait until the server lets us know about
103+ # this id.
104+ raise UnknownPackageData(hash)
105+ if package.installed:
106+ # Package currently installed. Must remove it.
107+ result.removals.append(id)
108+ else:
109+ # Package currently available. Must install it.
110+ result.installs.append(id)
111+ if count == 1 and self.may_complement_changes(result, policy):
112+ # Mark all missing packages and try one more iteration
113+ self.mark_packages(install=result.installs,
114+ remove=result.removals, reset=False)
115 else:
116- # Package currently available. Must install it.
117- result.installs.append(id)
118- else:
119- result.code = SUCCESS_RESULT
120+ result.code = DEPENDENCY_ERROR_RESULT
121+ else:
122+ result.code = SUCCESS_RESULT
123
124 return result
125
126+ def may_complement_changes(self, result, policy):
127+ """Decide whether or not we should complement the given changes.
128+
129+ @param result: A L{PackagesResultObject} holding the details about the
130+ missing dependencies needed to complement the given changes.
131+ @param policy: It can be one of the following values:
132+ - L{POLICY_STRICT}, no additional packages will be marked.
133+ - L{POLICY_ALLOW_INSTALLS}, if only additional installs are missing
134+ they will be marked for installation.
135+ @return: A boolean indicating whether the given policy allows to
136+ complement the changes and retry.
137+ """
138+ if policy == POLICY_ALLOW_INSTALLS:
139+ if result.installs and not result.removals:
140+ return True
141+ return False
142+
143 def handle_change_packages(self, message):
144 """Handle a C{change-packages} message."""
145
146@@ -233,7 +264,7 @@
147 message.get("install", ()),
148 message.get("remove", ()))
149
150- result = self.change_packages()
151+ result = self.change_packages(message.get("policy", POLICY_STRICT))
152
153 response = {"type": "change-packages-result",
154 "operation-id": message.get("operation-id")}
155
156=== modified file 'landscape/package/tests/test_changer.py'
157--- landscape/package/tests/test_changer.py 2010-02-05 19:05:47 +0000
158+++ landscape/package/tests/test_changer.py 2010-02-08 21:50:24 +0000
159@@ -9,11 +9,13 @@
160 from smart.cache import Provides
161
162 from landscape.package.changer import (
163- PackageChanger, main, find_changer_command, UNKNOWN_PACKAGE_DATA_TIMEOUT)
164+ PackageChanger, main, find_changer_command, UNKNOWN_PACKAGE_DATA_TIMEOUT,
165+ SUCCESS_RESULT, DEPENDENCY_ERROR_RESULT, POLICY_ALLOW_INSTALLS)
166 from landscape.package.store import PackageStore
167 from landscape.package.facade import (
168 DependencyError, TransactionError, SmartError)
169-from landscape.package.changer import PackageChangerConfiguration
170+from landscape.package.changer import (
171+ PackageChangerConfiguration, ChangePackagesResult)
172
173 from landscape.tests.mocker import ANY
174 from landscape.tests.helpers import (
175@@ -306,6 +308,101 @@
176 "type": "change-packages-result"}])
177 return result.addCallback(got_result)
178
179+ def test_perform_changes_with_allow_install_policy(self):
180+ """
181+ The C{POLICY_ALLOW_INSTALLS} policy the makes the changer mark
182+ the missing packages for installation.
183+ """
184+ self.store.set_hash_ids({HASH1: 1})
185+ self.facade.reload_channels()
186+ package1 = self.facade.get_packages_by_name("name1")[0]
187+
188+ self.mocker.order()
189+ self.facade.perform_changes = self.mocker.mock()
190+ self.facade.perform_changes()
191+ self.mocker.throw(DependencyError([package1]))
192+
193+ self.facade.mark_install = self.mocker.mock()
194+ self.facade.mark_install(package1)
195+ self.facade.perform_changes()
196+ self.mocker.result("success")
197+ self.mocker.replay()
198+
199+ result = self.changer.change_packages(POLICY_ALLOW_INSTALLS)
200+
201+ self.assertEquals(result.code, SUCCESS_RESULT)
202+ self.assertEquals(result.text, "success")
203+ self.assertEquals(result.installs, [1])
204+ self.assertEquals(result.removals, [])
205+
206+ def test_perform_changes_with_allow_install_policy_and_removals(self):
207+ """
208+ The C{POLICY_ALLOW_INSTALLS} policy doesn't allow additional packages
209+ to be removed.
210+ """
211+ self.store.set_hash_ids({HASH1: 1, HASH2: 2})
212+ self.set_pkg1_installed()
213+ self.facade.reload_channels()
214+
215+ package1 = self.facade.get_packages_by_name("name1")[0]
216+ package2 = self.facade.get_packages_by_name("name2")[0]
217+ self.facade.perform_changes = self.mocker.mock()
218+ self.facade.perform_changes()
219+ self.mocker.throw(DependencyError([package1, package2]))
220+ self.mocker.replay()
221+
222+ result = self.changer.change_packages(POLICY_ALLOW_INSTALLS)
223+
224+ self.assertEquals(result.code, DEPENDENCY_ERROR_RESULT)
225+ self.assertEquals(result.text, None)
226+ self.assertEquals(result.installs, [2])
227+ self.assertEquals(result.removals, [1])
228+
229+ def test_perform_changes_with_max_retries(self):
230+ """
231+ After having complemented the requested changes to handle a dependency
232+ error, the L{PackageChanger.change_packages} will try to perform the
233+ requested changes again only once.
234+ """
235+ self.store.set_hash_ids({HASH1: 1, HASH2: 2})
236+ self.facade.reload_channels()
237+
238+ package1 = self.facade.get_packages_by_name("name1")[0]
239+ package2 = self.facade.get_packages_by_name("name2")[0]
240+
241+ self.facade.perform_changes = self.mocker.mock()
242+ self.facade.perform_changes()
243+ self.mocker.throw(DependencyError([package1]))
244+ self.facade.perform_changes()
245+ self.mocker.throw(DependencyError([package2]))
246+ self.mocker.replay()
247+
248+ result = self.changer.change_packages(POLICY_ALLOW_INSTALLS)
249+
250+ self.assertEquals(result.code, DEPENDENCY_ERROR_RESULT)
251+ self.assertEquals(result.text, None)
252+ self.assertEquals(result.installs, [1, 2])
253+ self.assertEquals(result.removals, [])
254+
255+ def test_handle_change_packages_with_policy(self):
256+ """
257+ The C{change-packages} message can have an optional C{policy}
258+ field that will be passed to the C{perform_changes} method.
259+ """
260+ self.store.set_hash_ids({HASH1: 1})
261+ self.store.add_task("changer",
262+ {"type": "change-packages",
263+ "install": [1],
264+ "policy": POLICY_ALLOW_INSTALLS,
265+ "operation-id": 123})
266+ self.changer.change_packages = self.mocker.mock()
267+ self.changer.change_packages(POLICY_ALLOW_INSTALLS)
268+ result = ChangePackagesResult()
269+ result.code = SUCCESS_RESULT
270+ self.mocker.result(result)
271+ self.mocker.replay()
272+ return self.changer.handle_tasks()
273+
274 def test_transaction_error(self):
275 """
276 In this case, the package we're trying to install declared some
277@@ -621,7 +718,7 @@
278 ".run_task_handler",
279 passthrough=False)
280 setsid = self.mocker.replace("os.setsid")
281-
282+
283 setsid()
284 run_task_handler(PackageChanger, ["ARGS"])
285 self.mocker.result("RESULT")

Subscribers

People subscribed via source and target branches

to all changes: