10
10
from landscape.lib.twisted_util import gather_results, spawn_process
11
11
from landscape.lib.fetch import fetch_async
12
12
from landscape.lib.fs import touch_file
13
from landscape.lib import bpickle
15
from landscape.package.facade import AptFacade
14
16
from landscape.package.taskhandler import (
15
17
PackageTaskHandlerConfiguration, PackageTaskHandler, run_task_handler)
16
from landscape.package.store import UnknownHashIDRequest
18
from landscape.package.store import UnknownHashIDRequest, FakePackageStore
19
21
HASH_ID_REQUEST_TIMEOUT = 7200
49
51
smart_update_interval = 60
50
52
smart_update_filename = "/usr/lib/landscape/smart-update"
53
apt_update_filename = "/usr/lib/landscape/apt-update"
51
54
sources_list_filename = "/etc/apt/sources.list"
52
55
sources_list_directory = "/etc/apt/sources.list.d"
55
58
result = Deferred()
57
# Run smart-update before anything else, to make sure that
58
# the SmartFacade will load freshly updated channels
59
result.addCallback(lambda x: self.run_smart_update())
60
if isinstance(self._facade, AptFacade):
61
# Update APT cache if APT facade is enabled.
62
result.addCallback(lambda x: self.run_apt_update())
64
# Run smart-update before anything else, to make sure that
65
# the SmartFacade will load freshly updated channels
66
result.addCallback(lambda x: self.run_smart_update())
61
68
# If the appropriate hash=>id db is not there, fetch it
62
69
result.addCallback(lambda x: self.fetch_hash_id_db())
198
208
self.smart_update_filename, code, err))
199
209
logging.debug("'%s' exited with status %d (out='%s', err='%s'" % (
200
210
self.smart_update_filename, code, out, err))
201
touch_file(self._config.smart_update_stamp_filename)
211
touch_file(self._config.update_stamp_filename)
212
if not smart_failed and not self._facade.get_channels():
214
err = "There are no APT sources configured in %s or %s." % (
215
self.sources_list_filename, self.sources_list_directory)
202
216
deferred = self._broker.call_if_accepted(
203
217
"package-reporter-result", self.send_result, code, err)
204
218
deferred.addCallback(lambda ignore: (out, err, code))
207
221
result.addCallback(callback)
224
def _apt_update_timeout_expired(self, interval):
225
"""Check if the apt-update timeout has passed."""
226
stamp = self._config.update_stamp_filename
228
if not os.path.exists(stamp):
230
# check stamp file mtime
231
last_update = os.stat(stamp)[8]
232
now = int(time.time())
233
return (last_update + interval * 60) < now
235
def run_apt_update(self):
236
"""Run apt-update and log a warning in case of non-zero exit code.
238
@return: a deferred returning (out, err, code)
240
if (self._config.force_smart_update or self._apt_sources_have_changed()
241
or self._apt_update_timeout_expired(self.smart_update_interval)):
243
result = spawn_process(self.apt_update_filename)
245
def callback((out, err, code)):
246
touch_file(self._config.update_stamp_filename)
248
"'%s' exited with status %d (out='%s', err='%s')" % (
249
self.apt_update_filename, code, out, err))
252
logging.warning("'%s' exited with status %d (%s)" % (
253
self.apt_update_filename, code, err))
254
elif not self._facade.get_channels():
256
err = ("There are no APT sources configured in %s or %s." %
257
(self.sources_list_filename,
258
self.sources_list_directory))
260
deferred = self._broker.call_if_accepted(
261
"package-reporter-result", self.send_result, code, err)
262
deferred.addCallback(lambda ignore: (out, err, code))
265
return result.addCallback(callback)
268
logging.debug("'%s' didn't run, update interval has not passed" %
269
self.apt_update_filename)
270
return succeed(("", "", 0))
210
272
def send_result(self, code, err):
212
274
Report the package reporter result to the server in a message.
385
447
"""Create a hash_id_request and send message with "request-id"."""
386
448
request = self._store.add_hash_id_request(unknown_hashes)
387
449
message["request-id"] = request.id
388
result = self._broker.send_message(message, True)
450
result = self.send_message(message)
390
452
def set_message_id(message_id):
391
453
request.message_id = message_id
454
516
hash = self._facade.get_package_hash(package)
455
517
id = self._store.get_hash_id(hash)
456
518
if id is not None:
457
if package.installed:
519
if self._facade.is_package_installed(package):
458
520
current_installed.add(id)
459
for loader in package.loaders:
460
# Is the package also in a non-installed
461
# loader? IOW, "available".
462
if not loader.getInstalled():
463
current_available.add(id)
521
if self._facade.is_package_available(package):
522
current_available.add(id)
466
524
current_available.add(id)
468
526
# Are there any packages that this package is an upgrade for?
469
for upgrade in package.upgrades:
470
for provides in upgrade.providedby:
471
for provides_package in provides.packages:
472
if provides_package.installed:
473
current_upgrades.add(id)
527
if self._facade.is_package_upgrade(package):
528
current_upgrades.add(id)
482
530
for package in self._facade.get_locked_packages():
483
531
hash = self._facade.get_package_hash(package)
526
574
return succeed(False)
528
576
message["type"] = "packages"
529
result = self._broker.send_message(message, True)
577
result = self.send_message(message)
531
579
logging.info("Queuing message with changes in known packages: "
532
580
"%d installed, %d available, %d available upgrades, "
591
639
return succeed(False)
593
641
message["type"] = "package-locks"
594
result = self._broker.send_message(message, True)
642
result = self.send_message(message)
596
644
logging.info("Queuing message with changes in known package locks:"
597
645
" %d created, %d deleted." %
660
class FakeGlobalReporter(PackageReporter):
662
A standard reporter, which additionally stores messages sent into its
666
package_store_class = FakePackageStore
668
def send_message(self, message):
669
self._store.save_message(message)
670
return super(FakeGlobalReporter, self).send_message(message)
673
class FakeReporter(PackageReporter):
675
A fake reporter which only sends messages previously stored by a
676
L{FakeGlobalReporter}.
679
package_store_class = FakePackageStore
680
global_store_filename = None
683
result = succeed(None)
685
# If the appropriate hash=>id db is not there, fetch it
686
result.addCallback(lambda x: self.fetch_hash_id_db())
688
result.addCallback(lambda x: self._store.clear_tasks())
690
# Finally, verify if we have anything new to send to the server.
691
result.addCallback(lambda x: self.send_pending_messages())
695
def send_pending_messages(self):
697
As the last callback of L{PackageReporter}, sends messages stored.
699
if self.global_store_filename is None:
700
self.global_store_filename = os.environ["FAKE_PACKAGE_STORE"]
701
if not os.path.exists(self.global_store_filename):
703
message_sent = set(self._store.get_message_ids())
704
global_store = FakePackageStore(self.global_store_filename)
705
all_message_ids = set(global_store.get_message_ids())
706
not_sent = all_message_ids - message_sent
707
deferred = succeed(None)
710
messages = global_store.get_messages_by_ids(not_sent)
712
for message_id, message in messages:
713
message = bpickle.loads(str(message))
714
if message["type"] not in got_type:
715
got_type.add(message["type"])
716
sent.append(message_id)
717
deferred.addCallback(
718
lambda x, message=message: self.send_message(message))
719
self._store.save_message_ids(sent)
613
return run_task_handler(PackageReporter, args)
724
if "FAKE_GLOBAL_PACKAGE_STORE" in os.environ:
725
return run_task_handler(FakeGlobalReporter, args)
726
elif "FAKE_PACKAGE_STORE" in os.environ:
727
return run_task_handler(FakeReporter, args)
729
return run_task_handler(PackageReporter, args)
616
732
def find_reporter_command():