~ubuntu-branches/ubuntu/jaunty/landscape-client/jaunty-updates

« back to all changes in this revision

Viewing changes to landscape/package/tests/test_reporter.py

  • Committer: Bazaar Package Importer
  • Author(s): Free Ekanayaka
  • Date: 2009-09-21 17:59:31 UTC
  • mfrom: (1.2.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20090921175931-4ucv40j9ro26i3lm
Tags: 1.3.2.3-0ubuntu0.9.04.0
* New upstream release (LP: #347983):
  - Don't clear the hash_id_requests table upon resynchronize (LP #417122)
  - Include the README file in landscape-client (LP: #396260)
  - Fix client capturing stderr from run_command when constructing
    hash-id-databases url (LP: #397480)
  - Use substvars to conditionally depend on update-motd or
    libpam-modules (LP: #393454)
  - Fix reporting wrong version to the server (LP: #391225)
  - The init script does not wait for the network to be available
    before checking for EC2 user data (LP: #383336)
  - When the broker is restarted by the watchdog, the state of the client
    is inconsistent (LP: #380633)
  - Package stays unknown forever in the client with hash-id-databases
    support (LP: #381356)
  - Standard error not captured when calling smart-update (LP: #387441)
  - Changer calls reporter without switching groups, just user (LP: #388092)
  - Run smart update in the package-reporter instead of having a cronjob (LP: #362355)
  - Package changer does not inherit proxy settings (LP: #381241)
  - The ./test script doesn't work in landscape-client (LP: #381613)
  - The source package should build on all supported releases (LP: #385098)
  - Strip smart update's output (LP: #387331)
  - The fetch() timeout isn't based on activity (#389224)
  - Client can use a UUID of "None" when fetching the hash-id-database (LP: #381291)
  - Registration should use the fqdn rather than just the hostname (LP: #385730)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
import glob
2
2
import sys
3
3
import os
 
4
import logging
4
5
 
5
6
from twisted.internet.defer import Deferred
 
7
from twisted.internet import reactor
6
8
 
7
 
from landscape.lib.lock import lock_path
 
9
from landscape.lib.fetch import fetch_async, FetchError
 
10
from landscape.lib.command import CommandError
8
11
 
9
12
from landscape.package.store import PackageStore, UnknownHashIDRequest
10
13
from landscape.package.reporter import (
12
15
from landscape.package import reporter
13
16
from landscape.package.facade import SmartFacade
14
17
 
 
18
from landscape.deployment import Configuration
15
19
from landscape.broker.remote import RemoteBroker
16
20
 
17
21
from landscape.package.tests.helpers import (
30
34
        super(PackageReporterTest, self).setUp()
31
35
 
32
36
        self.store = PackageStore(self.makeFile())
33
 
        self.reporter = PackageReporter(self.store, self.facade, self.remote)
 
37
        self.config = Configuration()
 
38
        self.reporter = PackageReporter(self.store, self.facade, self.remote, self.config)
34
39
 
35
40
    def set_pkg2_upgrades_pkg1(self):
36
41
        previous = self.Facade.channels_reloaded
219
224
        deferred = self.reporter.handle_tasks()
220
225
        return deferred.addCallback(got_result)
221
226
 
 
227
    def test_fetch_hash_id_db(self):
 
228
 
 
229
        # Assume package_hash_id_url is set
 
230
        self.config.data_path = self.makeDir()
 
231
        self.config.package_hash_id_url = "http://fake.url/path/"
 
232
        os.makedirs(os.path.join(self.config.data_path, "package", "hash-id"))
 
233
        hash_id_db_filename = os.path.join(self.config.data_path, "package",
 
234
                                           "hash-id", "uuid_codename_arch")
 
235
 
 
236
        # Fake uuid, codename and arch
 
237
        message_store = self.broker_service.message_store
 
238
        message_store.set_server_uuid("uuid")
 
239
        command_mock = self.mocker.replace("landscape.lib.command.run_command")
 
240
        command_mock("lsb_release -cs")
 
241
        self.mocker.result("codename")
 
242
        self.facade.set_arch("arch")
 
243
 
 
244
        # Let's say fetch_async is successful
 
245
        hash_id_db_url = self.config.package_hash_id_url + "uuid_codename_arch"
 
246
        fetch_async_mock = self.mocker.replace("landscape.lib.fetch.fetch_async")
 
247
        fetch_async_mock(hash_id_db_url)
 
248
        fetch_async_result = Deferred()
 
249
        fetch_async_result.callback("hash-ids")
 
250
        self.mocker.result(fetch_async_result)
 
251
 
 
252
        # The download should be properly logged
 
253
        logging_mock = self.mocker.replace("logging.info")
 
254
        logging_mock("Downloaded hash=>id database from %s" % hash_id_db_url)
 
255
        self.mocker.result(None)
 
256
 
 
257
        # We don't have our hash=>id database yet
 
258
        self.assertFalse(os.path.exists(hash_id_db_filename))
 
259
 
 
260
        # Now go!
 
261
        self.mocker.replay()
 
262
        result = self.reporter.fetch_hash_id_db()
 
263
 
 
264
        # Check the database
 
265
        def callback(ignored):
 
266
            self.assertTrue(os.path.exists(hash_id_db_filename))
 
267
            self.assertEquals(open(hash_id_db_filename).read(), "hash-ids")
 
268
        result.addCallback(callback)
 
269
 
 
270
        return result
 
271
 
 
272
    def test_fetch_hash_id_db_does_not_download_twice(self):
 
273
 
 
274
        # Let's say that the hash=>id database is already there
 
275
        self.config.package_hash_id_url = "http://fake.url/path/"
 
276
        self.config.data_path = self.makeDir()
 
277
        os.makedirs(os.path.join(self.config.data_path, "package", "hash-id"))
 
278
        hash_id_db_filename = os.path.join(self.config.data_path, "package",
 
279
                                           "hash-id", "uuid_codename_arch")
 
280
        open(hash_id_db_filename, "w").write("test")
 
281
 
 
282
        # Fake uuid, codename and arch
 
283
        message_store = self.broker_service.message_store
 
284
        message_store.set_server_uuid("uuid")
 
285
        command_mock = self.mocker.replace("landscape.lib.command.run_command")
 
286
        command_mock("lsb_release -cs")
 
287
        self.mocker.result("codename")
 
288
        self.facade.set_arch("arch")
 
289
 
 
290
        # Intercept any call to fetch_async
 
291
        fetch_async_mock = self.mocker.replace("landscape.lib.fetch.fetch_async")
 
292
        fetch_async_mock(ANY)
 
293
 
 
294
        # Go!
 
295
        self.mocker.replay()
 
296
        result = self.reporter.fetch_hash_id_db()
 
297
 
 
298
        def callback(ignored):
 
299
            # Check that fetch_async hasn't been called
 
300
            self.assertRaises(AssertionError, self.mocker.verify)
 
301
            fetch_async(None)
 
302
 
 
303
            # The hash=>id database is still there
 
304
            self.assertEquals(open(hash_id_db_filename).read(), "test")
 
305
 
 
306
        result.addCallback(callback)
 
307
 
 
308
        return result
 
309
 
 
310
    def test_fetch_hash_id_db_undetermined_server_uuid(self):
 
311
        """
 
312
        If the server-uuid can't be determined for some reason, no download
 
313
        should be attempted and the failure should be properly logged.
 
314
        """
 
315
        message_store = self.broker_service.message_store
 
316
        message_store.set_server_uuid(None)
 
317
 
 
318
        logging_mock = self.mocker.replace("logging.warning")
 
319
        logging_mock("Couldn't determine which hash=>id database to use: "
 
320
                     "server UUID not available")
 
321
        self.mocker.result(None)
 
322
        self.mocker.replay()
 
323
 
 
324
        result = self.reporter.fetch_hash_id_db()
 
325
        return result
 
326
 
 
327
    def test_fetch_hash_id_db_undetermined_codename(self):
 
328
 
 
329
        # Fake uuid
 
330
        message_store = self.broker_service.message_store
 
331
        message_store.set_server_uuid("uuid")
 
332
 
 
333
        # Undetermined codename
 
334
        command_mock = self.mocker.replace("landscape.lib.command.run_command")
 
335
        command_mock("lsb_release -cs")
 
336
        command_error = CommandError("lsb_release -cs", 1, "error")
 
337
        self.mocker.throw(command_error)
 
338
 
 
339
        # The failure should be properly logged
 
340
        logging_mock = self.mocker.replace("logging.warning")
 
341
        logging_mock("Couldn't determine which hash=>id database to use: %s" %
 
342
                     str(command_error))
 
343
        self.mocker.result(None)
 
344
 
 
345
        # Go!
 
346
        self.mocker.replay()
 
347
        result = self.reporter.fetch_hash_id_db()
 
348
 
 
349
        return result
 
350
 
 
351
    def test_fetch_hash_id_db_undetermined_arch(self):
 
352
 
 
353
        # Fake uuid and codename
 
354
        message_store = self.broker_service.message_store
 
355
        message_store.set_server_uuid("uuid")
 
356
        command_mock = self.mocker.replace("landscape.lib.command.run_command")
 
357
        command_mock("lsb_release -cs")
 
358
        self.mocker.result("codename")
 
359
 
 
360
        # Undetermined arch
 
361
        self.facade.set_arch(None)
 
362
 
 
363
        # The failure should be properly logged
 
364
        logging_mock = self.mocker.replace("logging.warning")
 
365
        logging_mock("Couldn't determine which hash=>id database to use: "\
 
366
                     "unknown dpkg architecture")
 
367
        self.mocker.result(None)
 
368
 
 
369
        # Go!
 
370
        self.mocker.replay()
 
371
        result = self.reporter.fetch_hash_id_db()
 
372
 
 
373
        return result
 
374
 
 
375
    def test_fetch_hash_id_db_with_default_url(self):
 
376
 
 
377
        # Let's say package_hash_id_url is not set but url is
 
378
        self.config.data_path = self.makeDir()
 
379
        self.config.package_hash_id_url = None
 
380
        self.config.url = "http://fake.url/path/message-system/"
 
381
        os.makedirs(os.path.join(self.config.data_path, "package", "hash-id"))
 
382
        hash_id_db_filename = os.path.join(self.config.data_path, "package",
 
383
                                           "hash-id", "uuid_codename_arch")
 
384
 
 
385
        # Fake uuid, codename and arch
 
386
        message_store = self.broker_service.message_store
 
387
        message_store.set_server_uuid("uuid")
 
388
        command_mock = self.mocker.replace("landscape.lib.command.run_command")
 
389
        command_mock("lsb_release -cs")
 
390
        self.mocker.result("codename")
 
391
        self.facade.set_arch("arch")
 
392
 
 
393
        # Check fetch_async is called with the default url
 
394
        hash_id_db_url = "http://fake.url/path/hash-id-databases/" \
 
395
                         "uuid_codename_arch"
 
396
        fetch_async_mock = self.mocker.replace("landscape.lib.fetch.fetch_async")
 
397
        fetch_async_mock(hash_id_db_url)
 
398
        fetch_async_result = Deferred()
 
399
        fetch_async_result.callback("hash-ids")
 
400
        self.mocker.result(fetch_async_result)
 
401
 
 
402
        # Now go!
 
403
        self.mocker.replay()
 
404
        result = self.reporter.fetch_hash_id_db()
 
405
 
 
406
        # Check the database
 
407
        def callback(ignored):
 
408
            self.assertTrue(os.path.exists(hash_id_db_filename))
 
409
            self.assertEquals(open(hash_id_db_filename).read(), "hash-ids")
 
410
        result.addCallback(callback)
 
411
        return result
 
412
 
 
413
    def test_fetch_hash_id_db_with_download_error(self):
 
414
 
 
415
        # Assume package_hash_id_url is set
 
416
        self.config.data_path = self.makeDir()
 
417
        self.config.package_hash_id_url = "http://fake.url/path/"
 
418
 
 
419
        # Fake uuid, codename and arch
 
420
        message_store = self.broker_service.message_store
 
421
        message_store.set_server_uuid("uuid")
 
422
        command_mock = self.mocker.replace("landscape.lib.command.run_command")
 
423
        command_mock("lsb_release -cs")
 
424
        self.mocker.result("codename")
 
425
        self.facade.set_arch("arch")
 
426
 
 
427
        # Let's say fetch_async fails
 
428
        hash_id_db_url = self.config.package_hash_id_url + "uuid_codename_arch"
 
429
        fetch_async_mock = self.mocker.replace("landscape.lib.fetch.fetch_async")
 
430
        fetch_async_mock(hash_id_db_url)
 
431
        fetch_async_result = Deferred()
 
432
        fetch_async_result.errback(FetchError("fetch error"))
 
433
        self.mocker.result(fetch_async_result)
 
434
 
 
435
        # The failure should be properly logged
 
436
        logging_mock = self.mocker.replace("logging.warning")
 
437
        logging_mock("Couldn't download hash=>id database: fetch error")
 
438
        self.mocker.result(None)
 
439
 
 
440
        # Now go!
 
441
        self.mocker.replay()
 
442
        result = self.reporter.fetch_hash_id_db()
 
443
 
 
444
        # We shouldn't have any hash=>id database
 
445
        def callback(ignored):
 
446
            hash_id_db_filename = os.path.join(self.config.data_path, "package",
 
447
                                               "hash-id", "uuid_codename_arch")
 
448
            self.assertEquals(os.path.exists(hash_id_db_filename), False)
 
449
        result.addCallback(callback)
 
450
 
 
451
        return result
 
452
 
 
453
    def test_fetch_hash_id_db_with_undetermined_url(self):
 
454
 
 
455
        # We don't know where to fetch the hash=>id database from
 
456
        self.config.url = None
 
457
        self.config.package_hash_id_url = None
 
458
 
 
459
        # Fake uuid, codename and arch
 
460
        message_store = self.broker_service.message_store
 
461
        message_store.set_server_uuid("uuid")
 
462
        command_mock = self.mocker.replace("landscape.lib.command.run_command")
 
463
        command_mock("lsb_release -cs")
 
464
        self.mocker.result("codename")
 
465
        self.facade.set_arch("arch")
 
466
 
 
467
        # The failure should be properly logged
 
468
        logging_mock = self.mocker.replace("logging.warning")
 
469
        logging_mock("Can't determine the hash=>id database url")
 
470
        self.mocker.result(None)
 
471
 
 
472
        # Let's go
 
473
        self.mocker.replay()
 
474
 
 
475
        result = self.reporter.fetch_hash_id_db()
 
476
 
 
477
        # We shouldn't have any hash=>id database
 
478
        def callback(ignored):
 
479
            hash_id_db_filename = os.path.join(self.config.data_path, "package",
 
480
                                               "hash-id", "uuid_codename_arch")
 
481
            self.assertEquals(os.path.exists(hash_id_db_filename), False)
 
482
        result.addCallback(callback)
 
483
 
 
484
        return result
 
485
 
 
486
    def test_run_smart_update(self):
 
487
        """
 
488
        The L{PackageReporter.run_smart_update} method should run smart-update
 
489
        with the proper arguments.
 
490
        """
 
491
        self.reporter.smart_update_filename = self.makeFile(
 
492
            "#!/bin/sh\necho -n $@")
 
493
        os.chmod(self.reporter.smart_update_filename, 0755)
 
494
        logging_mock = self.mocker.replace("logging.debug")
 
495
        logging_mock("'%s' exited with status 0 (out='--after %d', err=''" % (
 
496
            self.reporter.smart_update_filename,
 
497
            self.reporter.smart_update_interval))
 
498
        self.mocker.replay()
 
499
        deferred = Deferred()
 
500
 
 
501
        def do_test():
 
502
            def raiseme(x):
 
503
                raise Exception
 
504
            logging.warning = raiseme
 
505
            result = self.reporter.run_smart_update()
 
506
            def callback((out, err, code)):
 
507
                interval = self.reporter.smart_update_interval
 
508
                self.assertEquals(out, "--after %d" % interval)
 
509
                self.assertEquals(err, "")
 
510
                self.assertEquals(code, 0)
 
511
            result.addCallback(callback)
 
512
            result.chainDeferred(deferred)
 
513
 
 
514
        reactor.callWhenRunning(do_test)
 
515
        return deferred
 
516
 
 
517
    def test_run_smart_update_warns_about_failures(self):
 
518
        """
 
519
        The L{PackageReporter.run_smart_update} method should log a warning
 
520
        in case smart-update terminates with a non-zero exit code other than 1.
 
521
        """
 
522
        self.reporter.smart_update_filename = self.makeFile(
 
523
            "#!/bin/sh\necho -n error >&2\necho -n output\nexit 2")
 
524
        os.chmod(self.reporter.smart_update_filename, 0755)
 
525
        logging_mock = self.mocker.replace("logging.warning")
 
526
        logging_mock("'%s' exited with status 2"
 
527
                     " (error)" % self.reporter.smart_update_filename)
 
528
        self.mocker.replay()
 
529
        deferred = Deferred()
 
530
 
 
531
        def do_test():
 
532
            result = self.reporter.run_smart_update()
 
533
            def callback((out, err, code)):
 
534
                interval = self.reporter.smart_update_interval
 
535
                self.assertEquals(out, "output")
 
536
                self.assertEquals(err, "error")
 
537
                self.assertEquals(code, 2)
 
538
            result.addCallback(callback)
 
539
            result.chainDeferred(deferred)
 
540
 
 
541
        reactor.callWhenRunning(do_test)
 
542
        return deferred
 
543
 
 
544
    def test_run_smart_update_warns_exit_code_1_and_non_empty_stderr(self):
 
545
        """
 
546
        The L{PackageReporter.run_smart_update} method should log a warning
 
547
        in case smart-update terminates with exit code 1 and non empty stderr.
 
548
        """
 
549
        self.reporter.smart_update_filename = self.makeFile(
 
550
            "#!/bin/sh\necho -n \"error  \" >&2\nexit 1")
 
551
        os.chmod(self.reporter.smart_update_filename, 0755)
 
552
        logging_mock = self.mocker.replace("logging.warning")
 
553
        logging_mock("'%s' exited with status 1"
 
554
                     " (error  )" % self.reporter.smart_update_filename)
 
555
        self.mocker.replay()
 
556
        deferred = Deferred()
 
557
        def do_test():
 
558
            result = self.reporter.run_smart_update()
 
559
            def callback((out, err, code)):
 
560
                interval = self.reporter.smart_update_interval
 
561
                self.assertEquals(out, "")
 
562
                self.assertEquals(err, "error  ")
 
563
                self.assertEquals(code, 1)
 
564
            result.addCallback(callback)
 
565
            result.chainDeferred(deferred)
 
566
 
 
567
        reactor.callWhenRunning(do_test)
 
568
        return deferred
 
569
 
 
570
    def test_run_smart_update_ignores_exit_code_1_and_empty_output(self):
 
571
        """
 
572
        The L{PackageReporter.run_smart_update} method should not log anything
 
573
        in case smart-update terminates with exit code 1 and output containing
 
574
        only a newline character.
 
575
        """
 
576
        self.reporter.smart_update_filename = self.makeFile(
 
577
            "#!/bin/sh\necho\nexit 1")
 
578
        os.chmod(self.reporter.smart_update_filename, 0755)
 
579
        deferred = Deferred()
 
580
        def do_test():
 
581
            def raiseme(x):
 
582
                raise Exception
 
583
            logging.warning = raiseme
 
584
            result = self.reporter.run_smart_update()
 
585
            def callback((out, err, code)):
 
586
                interval = self.reporter.smart_update_interval
 
587
                self.assertEquals(out, "\n")
 
588
                self.assertEquals(err, "")
 
589
                self.assertEquals(code, 1)
 
590
            result.addCallback(callback)
 
591
            result.chainDeferred(deferred)
 
592
 
 
593
        reactor.callWhenRunning(do_test)
 
594
        return deferred
 
595
 
222
596
    def test_remove_expired_hash_id_request(self):
223
597
        request = self.store.add_hash_id_request(["hash1"])
224
598
        request.message_id = 9999
569
943
 
570
944
        self.mocker.order()
571
945
 
572
 
        results = [Deferred() for i in range(4)]
 
946
        results = [Deferred() for i in range(7)]
 
947
 
 
948
        reporter_mock.run_smart_update()
 
949
        self.mocker.result(results[0])
 
950
 
 
951
        reporter_mock.fetch_hash_id_db()
 
952
        self.mocker.result(results[1])
 
953
 
 
954
        reporter_mock.use_hash_id_db()
 
955
        self.mocker.result(results[2])
573
956
 
574
957
        reporter_mock.handle_tasks()
575
 
        self.mocker.result(results[0])
 
958
        self.mocker.result(results[3])
576
959
 
577
960
        reporter_mock.remove_expired_hash_id_requests()
578
 
        self.mocker.result(results[1])
 
961
        self.mocker.result(results[4])
579
962
 
580
963
        reporter_mock.request_unknown_hashes()
581
 
        self.mocker.result(results[2])
 
964
        self.mocker.result(results[5])
582
965
 
583
966
        reporter_mock.detect_changes()
584
 
        self.mocker.result(results[3])
 
967
        self.mocker.result(results[6])
585
968
 
586
969
        self.mocker.replay()
587
970
 
625
1008
    def test_resynchronize(self):
626
1009
        """
627
1010
        When a resynchronize task arrives, the reporter should clear
628
 
        out all the data in the package store, except the hash ids.
 
1011
        out all the data in the package store, except the hash ids and
 
1012
        the hash ids requests.
629
1013
        This is done in the reporter so that we know it happens when
630
1014
        no other reporter is possibly running at the same time.
631
1015
        """
636
1020
        request1 = self.store.add_hash_id_request(["hash3"])
637
1021
        request2 = self.store.add_hash_id_request(["hash4"])
638
1022
 
 
1023
        # Set the message id to avoid the requests being deleted by the
 
1024
        # L{PackageReporter.remove_expired_hash_id_requests} method.
 
1025
        request1.message_id = 1
 
1026
        request2.message_id = 2
 
1027
 
639
1028
        # Let's make sure the data is there.
640
1029
        self.assertEquals(self.store.get_available_upgrades(), [2])
641
1030
        self.assertEquals(self.store.get_available(), [1])
643
1032
        self.assertEquals(self.store.get_hash_id_request(request1.id).id, request1.id)
644
1033
 
645
1034
        self.store.add_task("reporter", {"type": "resynchronize"})
646
 
        
 
1035
 
647
1036
        deferred = self.reporter.run()
648
1037
 
649
1038
        def check_result(result):
659
1048
            self.assertEquals(self.store.get_available(), [3, 4])
660
1049
            self.assertEquals(self.store.get_installed(), [])
661
1050
 
662
 
            # A New hash id request should also be detected for HASH3,
663
 
            # but there should be no other hash id requests.
664
 
            request = self.store.get_hash_id_request(request1.id)
665
 
            self.assertEquals(request.id, request1.id)
666
 
            self.assertEquals(request.hashes, [HASH3])
667
 
            self.assertRaises(UnknownHashIDRequest,
668
 
                              self.store.get_hash_id_request, request2.id)
 
1051
            # The two original hash id requests should be still there, and
 
1052
            # a new hash id request should also be detected for HASH3.
 
1053
            requests_count = 0
 
1054
            new_request_found = False
 
1055
            for request in self.store.iter_hash_id_requests():
 
1056
                requests_count += 1
 
1057
                if request.id == request1.id:
 
1058
                    self.assertEquals(request.hashes, ["hash3"])
 
1059
                elif request.id == request2.id:
 
1060
                    self.assertEquals(request.hashes, ["hash4"])
 
1061
                elif not new_request_found:
 
1062
                    self.assertEquals(request.hashes, [HASH3])
 
1063
                else:
 
1064
                    self.fail("Unexpected hash-id request!")
 
1065
            self.assertEquals(requests_count, 3)
 
1066
 
669
1067
        deferred.addCallback(check_result)
670
1068
        return deferred
671
1069