1
# -*- encoding: utf-8 -*-
7
from twisted.internet.defer import Deferred
9
from smart.cache import Provides
11
from landscape.lib.fs import touch_file
12
from landscape.package.changer import (
13
PackageChanger, main, find_changer_command, UNKNOWN_PACKAGE_DATA_TIMEOUT,
14
SUCCESS_RESULT, DEPENDENCY_ERROR_RESULT, POLICY_ALLOW_INSTALLS,
15
POLICY_ALLOW_ALL_CHANGES)
16
from landscape.package.store import PackageStore
17
from landscape.package.facade import (
18
DependencyError, TransactionError, SmartError)
19
from landscape.package.changer import (
20
PackageChangerConfiguration, ChangePackagesResult)
21
from landscape.tests.mocker import ANY
22
from landscape.tests.helpers import (
23
LandscapeTest, BrokerServiceHelper)
24
from landscape.package.tests.helpers import (
25
SmartFacadeHelper, HASH1, HASH2, HASH3, PKGDEB1, PKGDEB2, PKGNAME2)
26
from landscape.manager.manager import SUCCEEDED
29
class PackageChangerTest(LandscapeTest):
31
helpers = [SmartFacadeHelper, BrokerServiceHelper]
37
self.store = PackageStore(self.makeFile())
38
self.config = PackageChangerConfiguration()
39
self.config.data_path = self.makeDir()
40
os.mkdir(self.config.package_directory)
41
os.mkdir(self.config.binaries_path)
42
touch_file(self.config.smart_update_stamp_filename)
43
self.changer = PackageChanger(
44
self.store, self.facade, self.remote, self.config)
45
service = self.broker_service
46
service.message_store.set_accepted_types(["change-packages-result",
49
result = super(PackageChangerTest, self).setUp()
50
return result.addCallback(set_up)
52
def get_pending_messages(self):
53
return self.broker_service.message_store.get_pending_messages()
55
def set_pkg1_installed(self):
56
previous = self.Facade.channels_reloaded
60
self.get_packages_by_name("name1")[0].installed = True
61
self.Facade.channels_reloaded = callback
63
def set_pkg2_upgrades_pkg1(self):
64
previous = self.Facade.channels_reloaded
67
from smart.backends.deb.base import DebUpgrades
69
pkg2 = self.get_packages_by_name("name2")[0]
70
pkg2.upgrades += (DebUpgrades("name1", "=", "version1-release1"),)
71
self.reload_cache() # Relink relations.
72
self.Facade.channels_reloaded = callback
74
def set_pkg2_satisfied(self):
75
previous = self.Facade.channels_reloaded
79
pkg2 = self.get_packages_by_name("name2")[0]
81
self.reload_cache() # Relink relations.
82
self.Facade.channels_reloaded = callback
84
def set_pkg1_and_pkg2_satisfied(self):
85
previous = self.Facade.channels_reloaded
90
provide1 = Provides("prerequirename1", "prerequireversion1")
91
provide2 = Provides("requirename1", "requireversion1")
92
pkg2 = self.get_packages_by_name("name2")[0]
93
pkg2.provides += (provide1, provide2)
95
provide1 = Provides("prerequirename2", "prerequireversion2")
96
provide2 = Provides("requirename2", "requireversion2")
97
pkg1 = self.get_packages_by_name("name1")[0]
98
pkg1.provides += (provide1, provide2)
100
# Ask Smart to reprocess relationships.
102
self.Facade.channels_reloaded = callback
104
def test_unknown_package_id_for_dependency(self):
105
self.set_pkg1_and_pkg2_satisfied()
107
# Let's request an operation that would require an answer with a
108
# must-install field with a package for which the id isn't yet
109
# known by the client.
110
self.store.add_task("changer",
111
{"type": "change-packages", "install": [1],
112
"operation-id": 123})
114
# In our first try, we should get nothing, because the id of the
115
# dependency (HASH2) isn't known.
116
self.store.set_hash_ids({HASH1: 1})
117
result = self.changer.handle_tasks()
118
self.assertEquals(result.called, True)
119
self.assertMessages(self.get_pending_messages(), [])
121
self.assertIn("Package data not yet synchronized with server (%r)"
122
% HASH2, self.logfile.getvalue())
124
# So now we'll set it, and advance the reactor to the scheduled
125
# change detection. We'll get a lot of messages, including the
126
# result of our previous message, which got *postponed*.
127
self.store.set_hash_ids({HASH2: 2})
128
result = self.changer.handle_tasks()
130
def got_result(result):
131
self.assertMessages(self.get_pending_messages(),
132
[{"must-install": [2],
135
"type": "change-packages-result"}])
136
return result.addCallback(got_result)
138
def test_install_unknown_id(self):
139
self.store.add_task("changer",
140
{"type": "change-packages", "install": [456],
141
"operation-id": 123})
143
self.changer.handle_tasks()
145
self.assertIn("Package data not yet synchronized with server (456)",
146
self.logfile.getvalue())
147
self.assertTrue(self.store.get_next_task("changer"))
149
def test_remove_unknown_id(self):
150
self.store.add_task("changer",
151
{"type": "change-packages", "remove": [456],
152
"operation-id": 123})
154
self.changer.handle_tasks()
156
self.assertIn("Package data not yet synchronized with server (456)",
157
self.logfile.getvalue())
158
self.assertTrue(self.store.get_next_task("changer"))
160
def test_install_unknown_package(self):
161
self.store.set_hash_ids({"hash": 456})
162
self.store.add_task("changer",
163
{"type": "change-packages", "install": [456],
164
"operation-id": 123})
166
self.changer.handle_tasks()
168
self.assertIn("Package data not yet synchronized with server ('hash')",
169
self.logfile.getvalue())
170
self.assertTrue(self.store.get_next_task("changer"))
172
def test_remove_unknown_package(self):
173
self.store.set_hash_ids({"hash": 456})
174
self.store.add_task("changer",
175
{"type": "change-packages", "remove": [456],
176
"operation-id": 123})
178
self.changer.handle_tasks()
180
self.assertIn("Package data not yet synchronized with server ('hash')",
181
self.logfile.getvalue())
182
self.assertTrue(self.store.get_next_task("changer"))
184
def test_unknown_data_timeout(self):
185
"""After a while, unknown package data is reported as an error.
187
In these cases a warning is logged, and the task is removed.
189
self.store.add_task("changer",
190
{"type": "change-packages", "remove": [123],
191
"operation-id": 123})
193
time_mock = self.mocker.replace("time.time")
195
self.mocker.result(time.time() + UNKNOWN_PACKAGE_DATA_TIMEOUT)
196
self.mocker.count(1, None)
200
result = self.changer.handle_tasks()
203
# Reset it earlier so that Twisted has the true time function.
206
self.assertIn("Package data not yet synchronized with server (123)",
207
self.logfile.getvalue())
209
def got_result(result):
210
message = {"type": "change-packages-result",
213
"result-text": "Package data has changed. "
214
"Please retry the operation."}
215
self.assertMessages(self.get_pending_messages(), [message])
216
self.assertEquals(self.store.get_next_task("changer"), None)
217
return result.addCallback(got_result)
219
def test_dpkg_error(self):
221
Verify that errors emitted by dpkg are correctly reported to
222
the server as problems.
224
self.log_helper.ignore_errors(".*dpkg")
226
self.store.set_hash_ids({HASH1: 1})
227
self.store.add_task("changer",
228
{"type": "change-packages", "remove": [1],
229
"operation-id": 123})
231
self.set_pkg1_installed()
233
result = self.changer.handle_tasks()
235
def got_result(result):
236
messages = self.get_pending_messages()
237
self.assertEquals(len(messages), 1, "Too many messages")
238
message = messages[0]
239
self.assertEquals(message["operation-id"], 123)
240
self.assertEquals(message["result-code"], 100)
241
self.assertEquals(message["type"], "change-packages-result")
242
text = message["result-text"]
243
# We can't test the actual content of the message because the dpkg
244
# error can be localized
245
self.assertIn("\n[remove] name1_version1-release1\ndpkg: ", text)
246
self.assertIn("ERROR", text)
247
self.assertIn("(2)", text)
248
return result.addCallback(got_result)
250
def test_dependency_error(self):
252
In this test we hack the facade to simulate the situation where
253
Smart didn't accept to remove the package due to missing
254
dependencies that are present in the system but weren't requested
257
The client must answer it saying which additional changes are
258
needed to perform the requested operation.
260
It's a slightly hackish approach, since we're returning
261
the full set of packages available as a dependency error, but
262
it serves well for testing this specific feature.
264
self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3})
265
self.store.add_task("changer",
266
{"type": "change-packages", "install": [2],
267
"operation-id": 123})
269
self.set_pkg1_installed()
271
def raise_dependency_error(self):
272
raise DependencyError(self.get_packages())
273
self.Facade.perform_changes = raise_dependency_error
275
result = self.changer.handle_tasks()
277
def got_result(result):
278
self.assertMessages(self.get_pending_messages(),
279
[{"must-install": [2, 3],
283
"type": "change-packages-result"}])
284
return result.addCallback(got_result)
286
def test_dependency_error_with_binaries(self):
288
Simulate a failing operation involving server-generated binary
289
packages. The extra changes needed to perform the transaction
290
are sent back to the server.
292
os.remove(os.path.join(self.repository_dir, PKGNAME2))
293
self.store.set_hash_ids({HASH1: 1, HASH3: 3})
294
self.store.add_task("changer",
295
{"type": "change-packages",
297
"binaries": [(HASH2, 2, PKGDEB2)],
298
"operation-id": 123})
300
self.set_pkg1_installed()
302
def raise_dependency_error(self):
303
raise DependencyError(self.get_packages())
304
self.Facade.perform_changes = raise_dependency_error
306
result = self.changer.handle_tasks()
308
def got_result(result):
309
self.assertMessages(self.get_pending_messages(),
310
[{"must-install": [2, 3],
314
"type": "change-packages-result"}])
315
return result.addCallback(got_result)
317
def test_perform_changes_with_allow_install_policy(self):
319
The C{POLICY_ALLOW_INSTALLS} policy the makes the changer mark
320
the missing packages for installation.
322
self.store.set_hash_ids({HASH1: 1})
323
self.facade.reload_channels()
324
package1 = self.facade.get_packages_by_name("name1")[0]
327
self.facade.perform_changes = self.mocker.mock()
328
self.facade.perform_changes()
329
self.mocker.throw(DependencyError([package1]))
331
self.facade.mark_install = self.mocker.mock()
332
self.facade.mark_install(package1)
333
self.facade.perform_changes()
334
self.mocker.result("success")
337
result = self.changer.change_packages(POLICY_ALLOW_INSTALLS)
339
self.assertEquals(result.code, SUCCESS_RESULT)
340
self.assertEquals(result.text, "success")
341
self.assertEquals(result.installs, [1])
342
self.assertEquals(result.removals, [])
344
def test_perform_changes_with_allow_install_policy_and_removals(self):
346
The C{POLICY_ALLOW_INSTALLS} policy doesn't allow additional packages
349
self.store.set_hash_ids({HASH1: 1, HASH2: 2})
350
self.set_pkg1_installed()
351
self.facade.reload_channels()
353
package1 = self.facade.get_packages_by_name("name1")[0]
354
package2 = self.facade.get_packages_by_name("name2")[0]
355
self.facade.perform_changes = self.mocker.mock()
356
self.facade.perform_changes()
357
self.mocker.throw(DependencyError([package1, package2]))
360
result = self.changer.change_packages(POLICY_ALLOW_INSTALLS)
362
self.assertEquals(result.code, DEPENDENCY_ERROR_RESULT)
363
self.assertEquals(result.text, None)
364
self.assertEquals(result.installs, [2])
365
self.assertEquals(result.removals, [1])
367
def test_perform_changes_with_max_retries(self):
369
After having complemented the requested changes to handle a dependency
370
error, the L{PackageChanger.change_packages} will try to perform the
371
requested changes again only once.
373
self.store.set_hash_ids({HASH1: 1, HASH2: 2})
374
self.facade.reload_channels()
376
package1 = self.facade.get_packages_by_name("name1")[0]
377
package2 = self.facade.get_packages_by_name("name2")[0]
379
self.facade.perform_changes = self.mocker.mock()
380
self.facade.perform_changes()
381
self.mocker.throw(DependencyError([package1]))
382
self.facade.perform_changes()
383
self.mocker.throw(DependencyError([package2]))
386
result = self.changer.change_packages(POLICY_ALLOW_INSTALLS)
388
self.assertEquals(result.code, DEPENDENCY_ERROR_RESULT)
389
self.assertEquals(result.text, None)
390
self.assertEquals(result.installs, [1, 2])
391
self.assertEquals(result.removals, [])
393
def test_handle_change_packages_with_policy(self):
395
The C{change-packages} message can have an optional C{policy}
396
field that will be passed to the C{perform_changes} method.
398
self.store.set_hash_ids({HASH1: 1})
399
self.store.add_task("changer",
400
{"type": "change-packages",
402
"policy": POLICY_ALLOW_INSTALLS,
403
"operation-id": 123})
404
self.changer.change_packages = self.mocker.mock()
405
self.changer.change_packages(POLICY_ALLOW_INSTALLS)
406
result = ChangePackagesResult()
407
result.code = SUCCESS_RESULT
408
self.mocker.result(result)
410
return self.changer.handle_tasks()
412
def test_perform_changes_with_policy_allow_all_changes(self):
414
The C{POLICY_ALLOW_ALL_CHANGES} policy allows any needed additional
415
package to be installed or removed.
417
self.store.set_hash_ids({HASH1: 1, HASH2: 2})
418
self.set_pkg1_installed()
419
self.facade.reload_channels()
422
package1 = self.facade.get_packages_by_name("name1")[0]
423
package2 = self.facade.get_packages_by_name("name2")[0]
424
self.facade.perform_changes = self.mocker.mock()
425
self.facade.perform_changes()
426
self.mocker.throw(DependencyError([package1, package2]))
427
self.facade.mark_install = self.mocker.mock()
428
self.facade.mark_remove = self.mocker.mock()
429
self.facade.mark_install(package2)
430
self.facade.mark_remove(package1)
431
self.facade.perform_changes()
432
self.mocker.result("success")
435
result = self.changer.change_packages(POLICY_ALLOW_ALL_CHANGES)
437
self.assertEquals(result.code, SUCCESS_RESULT)
438
self.assertEquals(result.text, "success")
439
self.assertEquals(result.installs, [2])
440
self.assertEquals(result.removals, [1])
442
def test_transaction_error(self):
444
In this case, the package we're trying to install declared some
445
dependencies that can't be satisfied in the client because they
446
don't exist at all. The client must answer the request letting
447
the server know about the unsolvable problem.
449
self.store.set_hash_ids({HASH1: 1})
450
self.store.add_task("changer",
451
{"type": "change-packages", "install": [1],
452
"operation-id": 123})
454
result = self.changer.handle_tasks()
456
def got_result(result):
457
result_text = ("requirename1 = requireversion1")
458
messages = self.get_pending_messages()
459
self.assertEquals(len(messages), 1)
460
message = messages[0]
461
self.assertEquals(message["operation-id"], 123)
462
self.assertEquals(message["result-code"], 100)
463
self.assertIn(result_text, message["result-text"])
464
self.assertEquals(message["type"], "change-packages-result")
465
return result.addCallback(got_result)
467
def test_tasks_are_isolated(self):
469
Changes attempted on one task should be reset before the next
470
task is run. In this test, we try to run two different
471
operations, first installing package 2, then upgrading
472
anything available. The first installation will fail for lack
473
of superuser privileges, and the second one will succeed since
474
there's nothing to upgrade. If tasks are mixed up, the second
475
operation will fail too, because the installation of package 2
478
self.log_helper.ignore_errors(".*dpkg")
480
self.store.set_hash_ids({HASH1: 1, HASH2: 2})
482
self.store.add_task("changer",
483
{"type": "change-packages", "install": [2],
484
"operation-id": 123})
485
self.store.add_task("changer",
486
{"type": "change-packages", "upgrade-all": True,
487
"operation-id": 124})
489
self.set_pkg2_satisfied()
490
self.set_pkg1_installed()
492
result = self.changer.handle_tasks()
494
def got_result(result):
495
self.assertMessage(self.get_pending_messages()[1],
496
{"operation-id": 124,
498
"type": "change-packages-result"})
500
return result.addCallback(got_result)
502
def test_successful_operation(self):
503
"""Simulate a *very* successful operation.
505
We'll do that by hacking perform_changes(), and returning our
506
*very* successful operation result.
508
self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3})
509
self.store.add_task("changer",
510
{"type": "change-packages", "install": [2],
511
"operation-id": 123})
513
self.set_pkg1_installed()
515
def return_good_result(self):
516
return "Yeah, I did whatever you've asked for!"
517
self.Facade.perform_changes = return_good_result
519
result = self.changer.handle_tasks()
521
def got_result(result):
522
self.assertMessages(self.get_pending_messages(),
523
[{"operation-id": 123,
525
"result-text": "Yeah, I did whatever you've "
527
"type": "change-packages-result"}])
528
return result.addCallback(got_result)
530
def test_successful_operation_with_binaries(self):
532
Simulate a successful operation involving server-generated binary
535
self.store.set_hash_ids({HASH3: 3})
536
self.store.add_task("changer",
537
{"type": "change-packages", "install": [2, 3],
538
"binaries": [(HASH2, 2, PKGDEB2)],
539
"operation-id": 123})
541
def return_good_result(self):
542
return "Yeah, I did whatever you've asked for!"
543
self.Facade.perform_changes = return_good_result
545
result = self.changer.handle_tasks()
547
def got_result(result):
548
self.assertMessages(self.get_pending_messages(),
549
[{"operation-id": 123,
551
"result-text": "Yeah, I did whatever you've "
553
"type": "change-packages-result"}])
554
return result.addCallback(got_result)
556
def test_global_upgrade(self):
558
Besides asking for individual changes, the server may also request
559
the client to perform a global upgrade. This would be the equivalent
560
of a "smart upgrade" command being executed in the command line.
562
self.store.set_hash_ids({HASH1: 1, HASH2: 2})
564
self.store.add_task("changer",
565
{"type": "change-packages", "upgrade-all": True,
566
"operation-id": 123})
568
self.set_pkg2_upgrades_pkg1()
569
self.set_pkg2_satisfied()
570
self.set_pkg1_installed()
572
result = self.changer.handle_tasks()
574
def got_result(result):
575
self.assertMessages(self.get_pending_messages(),
576
[{"operation-id": 123,
579
"type": "change-packages-result"}])
581
return result.addCallback(got_result)
583
def test_global_upgrade_with_nothing_to_do(self):
585
self.store.add_task("changer",
586
{"type": "change-packages", "upgrade-all": True,
587
"operation-id": 123})
589
result = self.changer.handle_tasks()
591
def got_result(result):
592
self.assertMessages(self.get_pending_messages(),
593
[{"operation-id": 123,
595
"type": "change-packages-result"}])
597
return result.addCallback(got_result)
599
def test_run_with_no_smart_update_stamp(self):
601
If the smart-update stamp file is not there yet, the package changer
604
os.remove(self.config.smart_update_stamp_filename)
606
def assert_log(ignored):
607
self.assertIn("The package-reporter hasn't run yet, exiting.",
608
self.logfile.getvalue())
610
result = self.changer.run()
611
return result.addCallback(assert_log)
613
def test_spawn_reporter_after_running(self):
614
output_filename = self.makeFile("REPORTER NOT RUN")
615
reporter_filename = self.makeFile("#!/bin/sh\necho REPORTER RUN > %s" %
617
os.chmod(reporter_filename, 0755)
619
find_command_mock = self.mocker.replace(
620
"landscape.package.reporter.find_reporter_command")
622
self.mocker.result(reporter_filename)
625
# Add a task that will do nothing besides producing an answer.
626
# The reporter is only spawned if at least one task was handled.
627
self.store.add_task("changer", {"type": "change-packages",
628
"operation-id": 123})
630
result = self.changer.run()
632
def got_result(result):
633
self.assertEquals(open(output_filename).read().strip(),
635
return result.addCallback(got_result)
637
def test_spawn_reporter_after_running_with_config(self):
638
"""The changer passes the config to the reporter when running it."""
639
self.config.config = "test.conf"
640
output_filename = self.makeFile("REPORTER NOT RUN")
641
reporter_filename = self.makeFile("#!/bin/sh\necho ARGS $@ > %s" %
643
os.chmod(reporter_filename, 0755)
645
find_command_mock = self.mocker.replace(
646
"landscape.package.reporter.find_reporter_command")
648
self.mocker.result(reporter_filename)
651
# Add a task that will do nothing besides producing an answer.
652
# The reporter is only spawned if at least one task was handled.
653
self.store.add_task("changer", {"type": "change-packages",
654
"operation-id": 123})
656
result = self.changer.run()
658
def got_result(result):
659
self.assertEquals(open(output_filename).read().strip(),
661
return result.addCallback(got_result)
663
def test_set_effective_uid_and_gid_when_running_as_root(self):
665
After the package changer has run, we want the package-reporter to run
666
to report the recent changes. If we're running as root, we want to
667
change to the "landscape" user and "landscape" group. We also want to
668
deinitialize Smart to let the reporter run smart-update cleanly.
671
# We are running as root
672
getuid_mock = self.mocker.replace("os.getuid")
674
self.mocker.result(0)
676
# The order matters (first smart then gid and finally uid)
680
facade_mock = self.mocker.patch(self.facade)
683
# We want to return a known gid
684
grnam_mock = self.mocker.replace("grp.getgrnam")
685
grnam_mock("landscape")
687
class FakeGroup(object):
690
self.mocker.result(FakeGroup())
692
# First the changer should change the group
693
setgid_mock = self.mocker.replace("os.setgid")
696
# And a known uid as well
697
pwnam_mock = self.mocker.replace("pwd.getpwnam")
698
pwnam_mock("landscape")
700
class FakeUser(object):
703
self.mocker.result(FakeUser())
705
# And now the user as well
706
setuid_mock = self.mocker.replace("os.setuid")
709
# Finally, we don't really want the package reporter to run.
710
system_mock = self.mocker.replace("os.system")
715
# Add a task that will do nothing besides producing an answer.
716
# The reporter is only spawned if at least one task was handled.
717
self.store.add_task("changer", {"type": "change-packages",
718
"operation-id": 123})
719
return self.changer.run()
722
changer_mock = self.mocker.patch(self.changer)
726
results = [Deferred() for i in range(2)]
728
changer_mock.use_hash_id_db()
729
self.mocker.result(results[0])
731
changer_mock.handle_tasks()
732
self.mocker.result(results[1])
738
# It must raise an error because deferreds weren't yet fired.
739
self.assertRaises(AssertionError, self.mocker.verify)
741
for deferred in reversed(results):
742
deferred.callback(None)
744
def test_dont_spawn_reporter_after_running_if_nothing_done(self):
745
output_filename = self.makeFile("REPORTER NOT RUN")
746
reporter_filename = self.makeFile("#!/bin/sh\necho REPORTER RUN > %s" %
748
os.chmod(reporter_filename, 0755)
750
find_command_mock = self.mocker.replace(
751
"landscape.package.reporter.find_reporter_command")
753
self.mocker.result(reporter_filename)
754
self.mocker.count(0, None)
757
result = self.changer.run()
759
def got_result(result):
760
self.assertEquals(open(output_filename).read().strip(),
762
return result.addCallback(got_result)
767
run_task_handler = self.mocker.replace("landscape.package.taskhandler"
770
getpgrp = self.mocker.replace("os.getpgrp")
771
self.expect(getpgrp()).result(os.getpid() + 1)
772
setsid = self.mocker.replace("os.setsid")
774
run_task_handler(PackageChanger, ["ARGS"])
775
self.mocker.result("RESULT")
779
self.assertEquals(main(["ARGS"]), "RESULT")
781
def test_main_run_from_shell(self):
783
We want the getpid and getpgrp to return the same process id
784
this simulates the case where the process is already the process
785
session leader, in this case the os.setsid would fail.
787
getpgrp = self.mocker.replace("os.getpgrp")
789
self.mocker.result(os.getpid())
791
setsid = self.mocker.replace("os.setsid")
793
self.mocker.count(0, 0)
795
run_task_handler = self.mocker.replace("landscape.package.taskhandler"
798
run_task_handler(PackageChanger, ["ARGS"])
803
def test_find_changer_command(self):
804
dirname = self.makeDir()
805
filename = self.makeFile("", dirname=dirname,
806
basename="landscape-package-changer")
808
saved_argv = sys.argv
810
sys.argv = [os.path.join(dirname, "landscape-monitor")]
812
command = find_changer_command()
814
self.assertEquals(command, filename)
816
sys.argv = saved_argv
818
def test_transaction_error_with_unicode_data(self):
819
self.store.set_hash_ids({HASH1: 1})
820
self.store.add_task("changer",
821
{"type": "change-packages", "install": [1],
822
"operation-id": 123})
824
def raise_error(self):
825
raise TransactionError(u"áéíóú")
826
self.Facade.perform_changes = raise_error
828
result = self.changer.handle_tasks()
830
def got_result(result):
831
self.assertMessages(self.get_pending_messages(),
832
[{"operation-id": 123,
834
"result-text": u"áéíóú",
835
"type": "change-packages-result"}])
836
return result.addCallback(got_result)
838
def test_smart_error_with_unicode_data(self):
839
self.store.set_hash_ids({HASH1: 1})
840
self.store.add_task("changer",
841
{"type": "change-packages", "install": [1],
842
"operation-id": 123})
844
def raise_error(self):
845
raise SmartError(u"áéíóú")
846
self.Facade.perform_changes = raise_error
848
result = self.changer.handle_tasks()
850
def got_result(result):
851
self.assertMessages(self.get_pending_messages(),
852
[{"operation-id": 123,
854
"result-text": u"áéíóú",
855
"type": "change-packages-result"}])
856
return result.addCallback(got_result)
858
def test_smart_update_stamp_exists(self):
860
L{PackageChanger.smart_update_exists} returns C{True} if the
861
smart-update stamp file is there, C{False} otherwise.
863
self.assertTrue(self.changer.smart_update_stamp_exists())
864
os.remove(self.config.smart_update_stamp_filename)
865
self.assertFalse(self.changer.smart_update_stamp_exists())
867
def test_binaries_path(self):
869
self.config.binaries_path,
870
os.path.join(self.config.data_path, "package", "binaries"))
872
def test_init_channels(self):
874
The L{PackageChanger.init_channels} method makes the given
875
Debian packages available in a C{deb-dir} Smart channel.
877
binaries = [(HASH1, 111, PKGDEB1), (HASH2, 222, PKGDEB2)]
879
self.facade.reset_channels()
880
self.changer.init_channels(binaries)
882
binaries_path = self.config.binaries_path
883
self.assertFileContent(os.path.join(binaries_path, "111.deb"),
884
base64.decodestring(PKGDEB1))
885
self.assertFileContent(os.path.join(binaries_path, "222.deb"),
886
base64.decodestring(PKGDEB2))
887
self.assertEquals(self.facade.get_channels(),
888
{binaries_path: {"type": "deb-dir",
889
"path": binaries_path}})
891
self.assertEquals(self.store.get_hash_ids(), {HASH1: 111, HASH2: 222})
893
self.facade.ensure_channels_reloaded()
894
[pkg1, pkg2] = sorted(self.facade.get_packages(),
895
key=lambda pkg: pkg.name)
896
self.assertEquals(self.facade.get_package_hash(pkg1), HASH1)
897
self.assertEquals(self.facade.get_package_hash(pkg2), HASH2)
899
def test_init_channels_with_existing_hash_id_map(self):
901
The L{PackageChanger.init_channels} behaves well even if the
902
hash->id mapping for a given deb is already in the L{PackageStore}.
904
self.store.set_hash_ids({HASH1: 111})
905
self.changer.init_channels([(HASH1, 111, PKGDEB1)])
906
self.assertEquals(self.store.get_hash_ids(), {HASH1: 111})
908
def test_init_channels_with_existing_binaries(self):
910
The L{PackageChanger.init_channels} removes Debian packages
913
existing_deb_path = os.path.join(self.config.binaries_path, "123.deb")
914
self.makeFile(basename=existing_deb_path, content="foo")
915
self.changer.init_channels([])
916
self.assertFalse(os.path.exists(existing_deb_path))
918
def test_change_package_locks(self):
920
The L{PackageChanger.handle_tasks} method appropriately creates and
921
deletes package locks as requested by the C{change-package-locks}
924
self.facade.set_package_lock("bar")
925
self.store.add_task("changer", {"type": "change-package-locks",
926
"create": [("foo", ">=", "1.0")],
927
"delete": [("bar", None, None)],
928
"operation-id": 123})
930
def assert_result(result):
932
self.assertEquals(self.facade.get_package_locks(),
933
[("foo", ">=", "1.0")])
934
self.assertIn("Queuing message with change package locks results "
935
"to exchange urgently.", self.logfile.getvalue())
936
self.assertMessages(self.get_pending_messages(),
937
[{"type": "operation-result",
940
"result-text": "Package locks successfully"
944
result = self.changer.handle_tasks()
945
return result.addCallback(assert_result)
947
def test_change_package_locks_create_with_already_existing(self):
949
The L{PackageChanger.handle_tasks} method gracefully handles requests
950
for creating package locks that already exist.
952
self.facade.set_package_lock("foo")
953
self.store.add_task("changer", {"type": "change-package-locks",
954
"create": [("foo", None, None)],
955
"operation-id": 123})
957
def assert_result(result):
959
self.assertEquals(self.facade.get_package_locks(),
961
self.assertMessages(self.get_pending_messages(),
962
[{"type": "operation-result",
965
"result-text": "Package locks successfully"
969
result = self.changer.handle_tasks()
970
return result.addCallback(assert_result)
972
def test_change_package_locks_delete_without_already_existing(self):
974
The L{PackageChanger.handle_tasks} method gracefully handles requests
975
for deleting package locks that don't exist.
977
self.store.add_task("changer", {"type": "change-package-locks",
978
"delete": [("foo", ">=", "1.0")],
979
"operation-id": 123})
981
def assert_result(result):
983
self.assertEquals(self.facade.get_package_locks(), [])
984
self.assertMessages(self.get_pending_messages(),
985
[{"type": "operation-result",
988
"result-text": "Package locks successfully"
992
result = self.changer.handle_tasks()
993
return result.addCallback(assert_result)