Merge lp:~therve/landscape-client/restore-hardware-monitor into lp:~landscape/landscape-client/trunk

Proposed by Thomas Herve
Status: Merged
Approved by: Geoff Teale
Approved revision: 471
Merged at revision: 473
Proposed branch: lp:~therve/landscape-client/restore-hardware-monitor
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 1516 lines (+1060/-27)
32 files modified
README (+10/-1)
dbus/landscape.conf (+62/-0)
debian/landscape-client.install (+2/-0)
debian/landscape-client.postinst (+11/-0)
debian/rules (+9/-6)
landscape/broker/exchange.py (+3/-1)
landscape/hal.py (+52/-0)
landscape/lib/bpickle_dbus.py (+65/-0)
landscape/monitor/config.py (+5/-4)
landscape/monitor/hardwareinventory.py (+114/-0)
landscape/monitor/mountinfo.py (+65/-2)
landscape/monitor/tests/test_hardwareinventory.py (+273/-0)
landscape/monitor/tests/test_mountinfo.py (+76/-0)
landscape/monitor/tests/test_service.py (+9/-3)
landscape/package/releaseupgrader.py (+21/-0)
landscape/package/tests/test_releaseupgrader.py (+54/-0)
landscape/reactor.py (+11/-0)
landscape/service.py (+6/-0)
landscape/tests/test_configuration.py (+1/-1)
landscape/tests/test_hal.py (+87/-0)
landscape/tests/test_service.py (+9/-0)
landscape/tests/test_textmessage.py (+2/-2)
landscape/textmessage.py (+2/-1)
landscape/watchdog.py (+5/-2)
man/landscape-client.1 (+6/-1)
man/landscape-client.txt (+2/-0)
man/landscape-config.1 (+6/-1)
man/landscape-config.txt (+2/-0)
man/landscape-message.1 (+5/-1)
man/landscape-message.txt (+1/-0)
scripts/landscape-dbus-proxy (+82/-0)
setup.py (+2/-1)
To merge this branch: bzr merge lp:~therve/landscape-client/restore-hardware-monitor
Reviewer Review Type Date Requested Status
Geoff Teale (community) Approve
Fernando Correa Neto (community) Approve
Review via email: mp+95906@code.launchpad.net

Description of the change

The branch basically reverts r414 from trunk, except keeping the new hardwareinfo manager plugin.

To post a comment you must log in.
Revision history for this message
Fernando Correa Neto (fcorrea) wrote :

Everything went fine on my system (Oneiric)

PASSED (successes=2298)

+1!

review: Approve
Revision history for this message
Geoff Teale (tealeg) wrote :

+1 Good for me.

One small thing.

+ logging.error("Couldn't to connect to Hal via DBus")

The "to" there is unecessary.

review: Approve
472. By Thomas Herve

CLeanups

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'README'
--- README 2011-12-01 14:01:29 +0000
+++ README 2012-03-06 09:20:24 +0000
@@ -23,6 +23,14 @@
2323
24== Developing ==24== Developing ==
2525
26To run the full test suite, you must have a dbus session bus
27running. If you don't have one (for example, if you're running the
28tests in an ssh session), run the following command:
29
30export DBUS_SESSION_BUS_ADDRESS=`dbus-daemon --print-address=1 --session --fork`
31
32Then your tests should pass.
33
26When you want to test the landscape client manually without management34When you want to test the landscape client manually without management
27features, you can simply run:35features, you can simply run:
2836
@@ -31,7 +39,8 @@
31This defaults to the 'landscape-client.conf' configuration file.39This defaults to the 'landscape-client.conf' configuration file.
3240
33When you want to test management features manually, you'll need to run as root.41When you want to test management features manually, you'll need to run as root.
34There's a configuration file 'root-client.conf'.42There's a configuration file 'root-client.conf' which specifies use of the
43system bus.
3544
36$ sudo ./scripts/landscape-client -c root-client.conf45$ sudo ./scripts/landscape-client -c root-client.conf
3746
3847
=== added directory 'dbus'
=== added file 'dbus/landscape.conf'
--- dbus/landscape.conf 1970-01-01 00:00:00 +0000
+++ dbus/landscape.conf 2012-03-06 09:20:24 +0000
@@ -0,0 +1,62 @@
1<!DOCTYPE busconfig PUBLIC
2 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
3 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
4<busconfig>
5
6 <policy user="landscape">
7 <allow own="com.canonical.landscape.Broker" />
8 <allow own="com.canonical.landscape.Monitor" />
9
10 <allow send_destination="com.canonical.landscape.Broker" />
11 <allow receive_sender="com.canonical.landscape.Broker" />
12
13 <allow send_destination="com.canonical.landscape.Monitor" />
14 <allow receive_sender="com.canonical.landscape.Monitor" />
15
16 <allow send_destination="com.canonical.landscape.Manager" />
17 <allow receive_sender="com.canonical.landscape.Manager" />
18
19 <allow send_interface="org.freedesktop.Hal.Manager" />
20 <allow send_interface="org.freedesktop.Hal.Device" />
21
22 </policy>
23
24 <!-- this is a horrible hack -->
25 <policy user="haldaemon">
26
27 <allow receive_sender="com.canonical.landscape.Manager" />
28 <allow receive_sender="com.canonical.landscape.Monitor" />
29 <allow receive_sender="com.canonical.landscape.Broker" />
30
31 </policy>
32
33 <policy user="root">
34 <allow own="com.canonical.landscape.Manager" />
35
36 <allow send_destination="com.canonical.landscape.Broker" />
37 <allow receive_sender="com.canonical.landscape.Broker" />
38
39 <allow send_destination="com.canonical.landscape.Monitor" />
40 <allow receive_sender="com.canonical.landscape.Monitor" />
41
42 <allow send_destination="com.canonical.landscape.Manager" />
43 <allow receive_sender="com.canonical.landscape.Manager" />
44 </policy>
45
46 <policy context="default">
47 <deny own="com.canonical.landscape.Broker" />
48 <deny own="com.canonical.landscape.Monitor" />
49 <deny own="com.canonical.landscape.Manager" />
50
51 <deny send_destination="com.canonical.landscape.Broker" />
52 <deny receive_sender="com.canonical.landscape.Broker" />
53
54 <deny send_destination="com.canonical.landscape.Monitor" />
55 <deny receive_sender="com.canonical.landscape.Monitor" />
56
57 <deny send_destination="com.canonical.landscape.Manager" />
58 <deny receive_sender="com.canonical.landscape.Manager" />
59
60 </policy>
61
62</busconfig>
063
=== modified file 'debian/landscape-client.install'
--- debian/landscape-client.install 2011-12-01 14:01:29 +0000
+++ debian/landscape-client.install 2012-03-06 09:20:24 +0000
@@ -8,5 +8,7 @@
8usr/bin/landscape-package-reporter8usr/bin/landscape-package-reporter
9usr/bin/landscape-release-upgrader9usr/bin/landscape-release-upgrader
10usr/bin/landscape-is-cloud-managed10usr/bin/landscape-is-cloud-managed
11usr/bin/landscape-dbus-proxy
11usr/share/landscape/cloud-default.conf12usr/share/landscape/cloud-default.conf
13etc/dbus-1/system.d/landscape.conf
12usr/lib/landscape14usr/lib/landscape
1315
=== modified file 'debian/landscape-client.postinst'
--- debian/landscape-client.postinst 2012-02-28 17:47:57 +0000
+++ debian/landscape-client.postinst 2012-03-06 09:20:24 +0000
@@ -129,6 +129,17 @@
129 if [ -e $very_old_cron_job ]; then129 if [ -e $very_old_cron_job ]; then
130 rm $very_old_cron_job130 rm $very_old_cron_job
131 fi131 fi
132
133 # Check if we're upgrading from a D-Bus version
134 if ! [ -z $2 ]; then
135 if dpkg --compare-versions $2 lt 1.5.1; then
136 # Launch a proxy service that will forward requests over DBus
137 # from the old package-changer to the new AMP-based broker. This
138 # is a one-off only needed for the DBus->AMP upgrade
139 start-stop-daemon -x /usr/bin/landscape-dbus-proxy -b -c landscape -u landscape -S
140 fi
141 fi
142
132 ;;143 ;;
133144
134 abort-upgrade|abort-remove|abort-deconfigure)145 abort-upgrade|abort-remove|abort-deconfigure)
135146
=== modified file 'debian/rules'
--- debian/rules 2012-02-10 17:59:13 +0000
+++ debian/rules 2012-03-06 09:20:24 +0000
@@ -60,6 +60,7 @@
60 install -D -o root -g root -m 644 debian/cloud-default.conf $(root_dir)/usr/share/landscape/cloud-default.conf60 install -D -o root -g root -m 644 debian/cloud-default.conf $(root_dir)/usr/share/landscape/cloud-default.conf
61 install -D -o root -g root -m 755 smart-update/smart-update $(root_dir)/usr/lib/landscape/smart-update61 install -D -o root -g root -m 755 smart-update/smart-update $(root_dir)/usr/lib/landscape/smart-update
62 install -D -o root -g root -m 755 apt-update/apt-update $(root_dir)/usr/lib/landscape/apt-update62 install -D -o root -g root -m 755 apt-update/apt-update $(root_dir)/usr/lib/landscape/apt-update
63 install -D -o root -g root -m 644 dbus/landscape.conf $(root_dir)/etc/dbus-1/system.d/landscape.conf
6364
64binary-indep:65binary-indep:
65# do nothing66# do nothing
@@ -83,22 +84,24 @@
8384
84ifneq (,$(findstring $(dist_release),"dapper"))85ifneq (,$(findstring $(dist_release),"dapper"))
85 # We need python2.4-pysqlite2 and a non-buggy libcurl3-gnutls on dapper86 # We need python2.4-pysqlite2 and a non-buggy libcurl3-gnutls on dapper
86 echo "extra:Depends=python2.4-pysqlite2, libcurl3-gnutls (>= 7.15.1-1ubuntu3), python-smartpm (>= 1.1.1~bzr20081010-0ubuntu1.6.06.0)" >> $(landscape_common_substvars)87 echo "extra:Depends=python2.4-pysqlite2, libcurl3-gnutls (>= 7.15.1-1ubuntu3), python-smartpm (>= 1.1.1~bzr20081010-0ubuntu1.6.06.0), python2.4-dbus" >> $(landscape_common_substvars)
87 echo "extra:Depends=python2.4-pycurl" >> $(landscape_client_substvars)88 echo "extra:Depends=python2.4-pycurl, hal" >> $(landscape_client_substvars)
88endif89endif
89ifneq (,$(findstring $(dist_release),"hardy"))90ifneq (,$(findstring $(dist_release),"hardy"))
90 # We want the smart 1.1.1 from the Landscape repository on hardy91 # We want the smart 1.1.1 from the Landscape repository on hardy
91 echo "extra:Depends=python-smartpm (>= 1.1.1~bzr20081010-0ubuntu1.8.04.1)" >> $(landscape_common_substvars)92 echo "extra:Depends=python-smartpm (>= 1.1.1~bzr20081010-0ubuntu1.8.04.1), python-dbus" >> $(landscape_common_substvars)
92 echo "extra:Depends=python-pycurl" >> $(landscape_client_substvars)93 echo "extra:Depends=python-pycurl, hal" >> $(landscape_client_substvars)
93endif94endif
94ifneq (,$(filter $(dist_release),karmic lucid maverick))95ifneq (,$(filter $(dist_release),karmic lucid maverick))
95 # We want libpam-modules in karmic, and smart 1.296 # We want libpam-modules in karmic, and smart 1.2
96 echo "extra:Depends=libpam-modules (>= 1.0.1-9ubuntu3), python-smartpm (>= 1.2-4)" >> $(landscape_common_substvars)97 echo "extra:Depends=libpam-modules (>= 1.0.1-9ubuntu3), python-smartpm (>= 1.2-4), python-dbus" >> $(landscape_common_substvars)
97 echo "extra:Depends=python-pycurl" >> $(landscape_client_substvars)98 echo "extra:Depends=python-pycurl, hal" >> $(landscape_client_substvars)
98endif99endif
99ifeq (,$(filter $(dist_release),dapper hardy karmic lucid maverick))100ifeq (,$(filter $(dist_release),dapper hardy karmic lucid maverick))
101 # Starting natty, no more hal or dbus
100 echo "extra:Depends=libpam-modules (>= 1.0.1-9ubuntu3), python-smartpm (>= 1.2-4)" >> $(landscape_common_substvars)102 echo "extra:Depends=libpam-modules (>= 1.0.1-9ubuntu3), python-smartpm (>= 1.2-4)" >> $(landscape_common_substvars)
101 echo "extra:Depends=python-pycurl, gir1.2-gudev-1.0 (>= 165-0ubuntu2)" >> $(landscape_client_substvars)103 echo "extra:Depends=python-pycurl, gir1.2-gudev-1.0 (>= 165-0ubuntu2)" >> $(landscape_client_substvars)
104 echo "extra:Suggests=python-dbus, hal" >> $(landscape_client_substvars)
102endif105endif
103ifeq (,$(filter $(dist_release),dapper hardy karmic))106ifeq (,$(filter $(dist_release),dapper hardy karmic))
104 # The python-image-store-proxy package is needed for the eucalyptus plugin107 # The python-image-store-proxy package is needed for the eucalyptus plugin
105108
=== modified file 'landscape/broker/exchange.py'
--- landscape/broker/exchange.py 2011-12-01 14:01:29 +0000
+++ landscape/broker/exchange.py 2012-03-06 09:20:24 +0000
@@ -422,7 +422,9 @@
422 handler(message)422 handler(message)
423423
424 def register_client_accepted_message_type(self, type):424 def register_client_accepted_message_type(self, type):
425 self._client_accepted_types.add(type)425 # stringify the type because it's a dbus.String. It should work
426 # anyway, but this is just for sanity and less confusing logs.
427 self._client_accepted_types.add(str(type))
426428
427 def get_client_accepted_message_types(self):429 def get_client_accepted_message_types(self):
428 return sorted(self._client_accepted_types)430 return sorted(self._client_accepted_types)
429431
=== added file 'landscape/hal.py'
--- landscape/hal.py 1970-01-01 00:00:00 +0000
+++ landscape/hal.py 2012-03-06 09:20:24 +0000
@@ -0,0 +1,52 @@
1import logging
2
3from dbus import Interface, SystemBus
4from dbus.exceptions import DBusException
5
6
7class HALManager(object):
8
9 def __init__(self, bus=None):
10 try:
11 self._bus = bus or SystemBus()
12 manager = self._bus.get_object("org.freedesktop.Hal",
13 "/org/freedesktop/Hal/Manager")
14 except DBusException:
15 logging.error("Couldn't connect to Hal via DBus")
16 self._manager = None
17 else:
18 self._manager = Interface(manager, "org.freedesktop.Hal.Manager")
19
20 def get_devices(self):
21 """Returns a list of HAL devices.
22
23 @note: If it wasn't possible to connect to HAL over DBus, then an
24 empty list will be returned. This can happen if the HAL or DBus
25 services are not running.
26 """
27 if not self._manager:
28 return []
29 devices = []
30 for udi in self._manager.GetAllDevices():
31 device = self._bus.get_object("org.freedesktop.Hal", udi)
32 device = Interface(device, "org.freedesktop.Hal.Device")
33 device = HALDevice(device)
34 devices.append(device)
35 return devices
36
37
38class HALDevice(object):
39
40 def __init__(self, device):
41 self._children = []
42 self._device = device
43 self.properties = device.GetAllProperties()
44 self.udi = self.properties["info.udi"]
45 self.parent = None
46
47 def add_child(self, device):
48 self._children.append(device)
49 device.parent = self
50
51 def get_children(self):
52 return self._children
053
=== added file 'landscape/lib/bpickle_dbus.py'
--- landscape/lib/bpickle_dbus.py 1970-01-01 00:00:00 +0000
+++ landscape/lib/bpickle_dbus.py 2012-03-06 09:20:24 +0000
@@ -0,0 +1,65 @@
1"""
2Different versions of the Python DBus bindings return different types
3to represent integers, strings, lists, etc. Older versions return
4builtin Python types: C{int}, C{str}, C{list}, etc. Newer versions
5return DBus-specific wrappers: C{Int16}, C{String}, C{Array}, etc.
6Failures occur when DBus types are used because bpickle doesn't know
7that an C{Int16} is really an C{int} and that an C{Array} is really a
8C{list}.
9
10L{install} and L{uninstall} can install and remove extensions that
11make bpickle work with DBus types.
12"""
13
14import dbus
15
16from landscape.lib import bpickle
17
18
19def install():
20 """Install bpickle extensions for DBus types."""
21 for type, function in get_dbus_types():
22 bpickle.dumps_table[type] = function
23
24
25def uninstall():
26 """Uninstall bpickle extensions for DBus types."""
27 for type, function in get_dbus_types():
28 del bpickle.dumps_table[type]
29
30
31def dumps_utf8string(obj):
32 """
33 Convert the specified L{dbus.types.UTF8String} to bpickle's
34 representation for C{unicode} data.
35 """
36 return "u%s:%s" % (len(obj), obj)
37
38
39def dumps_double(obj):
40 """
41 Convert a dbus.types.Double into a floating point representation.
42 """
43 return "f%r;" % float(obj)
44
45
46def get_dbus_types():
47 """
48 Generator yields C{(type, bpickle_function)} for available DBus
49 types.
50 """
51 for (type_name, function) in [("Boolean", bpickle.dumps_bool),
52 ("Int16", bpickle.dumps_int),
53 ("UInt16", bpickle.dumps_int),
54 ("Int32", bpickle.dumps_int),
55 ("UInt32", bpickle.dumps_int),
56 ("Int64", bpickle.dumps_int),
57 ("UInt64", bpickle.dumps_int),
58 ("Double", dumps_double),
59 ("Array", bpickle.dumps_list),
60 ("Dictionary", bpickle.dumps_dict),
61 ("String", bpickle.dumps_unicode),
62 ("UTF8String", dumps_utf8string)]:
63 type = getattr(dbus.types, type_name, None)
64 if type is not None:
65 yield type, function
066
=== modified file 'landscape/monitor/config.py'
--- landscape/monitor/config.py 2011-11-30 09:28:10 +0000
+++ landscape/monitor/config.py 2012-03-06 09:20:24 +0000
@@ -1,10 +1,11 @@
1from landscape.deployment import Configuration1from landscape.deployment import Configuration
22
33
4ALL_PLUGINS = ["ActiveProcessInfo", "ComputerInfo", "LoadAverage",4ALL_PLUGINS = ["ActiveProcessInfo", "ComputerInfo", "HardwareInventory",
5 "MemoryInfo", "MountInfo", "ProcessorInfo", "Temperature",5 "LoadAverage", "MemoryInfo", "MountInfo", "ProcessorInfo",
6 "PackageMonitor", "UserMonitor", "RebootRequired",6 "Temperature", "PackageMonitor", "UserMonitor",
7 "AptPreferences", "NetworkActivity", "NetworkDevice"]7 "RebootRequired", "AptPreferences", "NetworkActivity",
8 "NetworkDevice"]
89
910
10class MonitorConfiguration(Configuration):11class MonitorConfiguration(Configuration):
1112
=== added file 'landscape/monitor/hardwareinventory.py'
--- landscape/monitor/hardwareinventory.py 1970-01-01 00:00:00 +0000
+++ landscape/monitor/hardwareinventory.py 2012-03-06 09:20:24 +0000
@@ -0,0 +1,114 @@
1import logging
2
3from twisted.internet.defer import succeed
4
5from landscape.lib.log import log_failure
6
7from landscape.diff import diff
8from landscape.monitor.plugin import MonitorPlugin
9
10
11class HardwareInventory(MonitorPlugin):
12
13 persist_name = "hardware-inventory"
14
15 def __init__(self, hal_manager=None):
16 super(HardwareInventory, self).__init__()
17 self._persist_sets = []
18 self._persist_removes = []
19 self.enabled = True
20 try:
21 from landscape.hal import HALManager
22 except ImportError:
23 self.enabled = False
24 else:
25 self._hal_manager = hal_manager or HALManager()
26
27 def register(self, manager):
28 if not self.enabled:
29 return
30 super(HardwareInventory, self).register(manager)
31 self.call_on_accepted("hardware-inventory", self.exchange, True)
32
33 def send_message(self, urgent):
34 devices = self.create_message()
35 if devices:
36 message = {"type": "hardware-inventory", "devices": devices}
37 result = self.registry.broker.send_message(message, urgent=urgent)
38 result.addCallback(self.persist_data)
39 result.addErrback(log_failure)
40 logging.info("Queueing a message with hardware-inventory "
41 "information.")
42 else:
43 result = succeed(None)
44 return result
45
46 def exchange(self, urgent=False):
47 if not self.enabled:
48 return
49 return self.registry.broker.call_if_accepted("hardware-inventory",
50 self.send_message, urgent)
51
52 def persist_data(self, message_id):
53 for key, udi, value in self._persist_sets:
54 self._persist.set((key, udi), value)
55 for key in self._persist_removes:
56 self._persist.remove(key)
57 del self._persist_sets[:]
58 del self._persist_removes[:]
59 # This forces the registry to write the persistent store to disk
60 # This means that the persistent data reflects the state of the
61 # messages sent.
62 self.registry.flush()
63
64 def create_message(self):
65 # FIXME Using persist to keep track of changes here uses a
66 # fair amount of memory. On my machine a rough test seemed to
67 # indicate that memory usage grew by 1.3mb, about 12% of the
68 # overall process size. Look here to save memory.
69 del self._persist_sets[:]
70 del self._persist_removes[:]
71 devices = []
72 previous_devices = self._persist.get("devices", {})
73 current_devices = set()
74
75 for device in self._hal_manager.get_devices():
76 previous_properties = previous_devices.get(device.udi)
77 if not previous_properties:
78 devices.append(("create", device.properties))
79 elif previous_properties != device.properties:
80 creates, updates, deletes = diff(previous_properties,
81 device.properties)
82 devices.append(("update", device.udi,
83 creates, updates, deletes))
84 current_devices.add(device.udi)
85 self._persist_sets.append(
86 ("devices", device.udi, device.properties))
87
88 items_with_parents = {}
89 deleted_devices = set()
90 for udi, value in previous_devices.iteritems():
91 if udi not in current_devices:
92 if "info.parent" in value:
93 items_with_parents[udi] = value["info.parent"]
94 deleted_devices.add(udi)
95
96 # We remove the deleted devices from our persistent store it's
97 # only the information we're sending to the server that we're
98 # compressing.
99 for udi in deleted_devices:
100 self._persist_removes.append(("devices", udi))
101
102 # We can now flatten the list of devices we send to the server
103 # For each of the items_with_parents, if both the item and it's parent
104 # are in the deleted_devices set, then we can remove this item from the
105 # set.
106 minimal_deleted_devices = deleted_devices.copy()
107 for child, parent in items_with_parents.iteritems():
108 if child in deleted_devices and parent in deleted_devices:
109 minimal_deleted_devices.remove(child)
110 # We now build the deleted devices message
111 for udi in minimal_deleted_devices:
112 devices.append(("delete", udi))
113
114 return devices
0115
=== modified file 'landscape/monitor/mountinfo.py'
--- landscape/monitor/mountinfo.py 2011-12-01 13:38:58 +0000
+++ landscape/monitor/mountinfo.py 2012-03-06 09:20:24 +0000
@@ -15,7 +15,7 @@
1515
16 def __init__(self, interval=300, monitor_interval=60 * 60,16 def __init__(self, interval=300, monitor_interval=60 * 60,
17 mounts_file="/proc/mounts", create_time=time.time,17 mounts_file="/proc/mounts", create_time=time.time,
18 statvfs=None, mtab_file="/etc/mtab"):18 statvfs=None, hal_manager=None, mtab_file="/etc/mtab"):
19 self.run_interval = interval19 self.run_interval = interval
20 self._monitor_interval = monitor_interval20 self._monitor_interval = monitor_interval
21 self._create_time = create_time21 self._create_time = create_time
@@ -29,6 +29,12 @@
29 self._mount_info = []29 self._mount_info = []
30 self._mount_info_to_persist = None30 self._mount_info_to_persist = None
31 try:31 try:
32 from landscape.hal import HALManager
33 except ImportError:
34 self._hal_manager = hal_manager
35 else:
36 self._hal_manager = hal_manager or HALManager()
37 try:
32 from gi.repository import GUdev38 from gi.repository import GUdev
33 except ImportError:39 except ImportError:
34 self._gudev_client = None40 self._gudev_client = None
@@ -116,7 +122,9 @@
116 current_mount_points.add(mount_point)122 current_mount_points.add(mount_point)
117123
118 def _get_removable_devices(self):124 def _get_removable_devices(self):
119 if self._gudev_client is not None:125 if self._hal_manager is not None:
126 return self._get_hal_removable_devices()
127 elif self._gudev_client is not None:
120 return self._get_udev_removable_devices()128 return self._get_udev_removable_devices()
121 else:129 else:
122 return set()130 return set()
@@ -130,6 +138,61 @@
130 return False138 return False
131 return is_removable()139 return is_removable()
132140
141 def _get_hal_removable_devices(self):
142 block_devices = {} # {udi: [device, ...]}
143 children = {} # {parent_udi: [child_udi, ...]}
144 removable = set()
145
146 # We walk the list of devices building up a dictionary of all removable
147 # devices, and a mapping of {UDI => [block devices]}
148 # We differentiate between devices that we definitely know are
149 # removable and devices that _may_ be removable, depending on their
150 # parent device, e.g. /dev/sdb1 isn't flagged as removable, but
151 # /dev/sdb may well be removable.
152
153 # Unfortunately, HAL doesn't guarantee the order of the devices
154 # returned from get_devices(), so we may not know that a parent device
155 # is removable when we find it's first child.
156 devices = self._hal_manager.get_devices()
157 for device in devices:
158 block_device = device.properties.get("block.device")
159 if block_device:
160 if device.properties.get("storage.removable"):
161 removable.add(device.udi)
162
163 try:
164 block_devices[device.udi].append(block_device)
165 except KeyError:
166 block_devices[device.udi] = [block_device]
167
168 parent_udi = device.properties.get("info.parent")
169 if parent_udi is not None:
170 try:
171 children[parent_udi].append(device.udi)
172 except KeyError:
173 children[parent_udi] = [device.udi]
174
175 # Propagate the removable flag from each node all the way to
176 # its leaf children.
177 updated = True
178 while updated:
179 updated = False
180 for parent_udi in children:
181 if parent_udi in removable:
182 for child_udi in children[parent_udi]:
183 if child_udi not in removable:
184 removable.add(child_udi)
185 updated = True
186
187 # We've now seen _all_ devices, and have the definitive list of
188 # removable UDIs, so we can now find all the removable devices in the
189 # system.
190 removable_devices = set()
191 for udi in removable:
192 removable_devices.update(block_devices[udi])
193
194 return removable_devices
195
133 def _get_mount_info(self):196 def _get_mount_info(self):
134 """Generator yields local mount points worth recording data for."""197 """Generator yields local mount points worth recording data for."""
135 removable_devices = self._get_removable_devices()198 removable_devices = self._get_removable_devices()
136199
=== added file 'landscape/monitor/tests/test_hardwareinventory.py'
--- landscape/monitor/tests/test_hardwareinventory.py 1970-01-01 00:00:00 +0000
+++ landscape/monitor/tests/test_hardwareinventory.py 2012-03-06 09:20:24 +0000
@@ -0,0 +1,273 @@
1from twisted.internet.defer import fail, succeed
2
3from landscape.monitor.hardwareinventory import HardwareInventory
4from landscape.tests.test_hal import MockHALManager, MockRealHALDevice
5from landscape.tests.helpers import LandscapeTest, MonitorHelper
6from landscape.tests.mocker import ANY
7from landscape.message_schemas import HARDWARE_INVENTORY
8
9
10class HardwareInventoryTest(LandscapeTest):
11
12 helpers = [MonitorHelper]
13
14 def setUp(self):
15 super(HardwareInventoryTest, self).setUp()
16 self.mstore.set_accepted_types(["hardware-inventory"])
17 devices = [MockRealHALDevice({u"info.udi": u"wubble",
18 u"info.product": u"Wubble"}),
19 MockRealHALDevice({u"info.udi": u"ooga",
20 u"info.product": u"Ooga"})]
21 self.hal_manager = MockHALManager(devices)
22 self.plugin = HardwareInventory(hal_manager=self.hal_manager)
23 self.monitor.add(self.plugin)
24
25 def assertSchema(self, devices):
26 full_message = {"type": "hardware-inventory", "devices": devices}
27 self.assertEqual(HARDWARE_INVENTORY.coerce(full_message),
28 full_message)
29
30 def test_hal_devices(self):
31 """
32 The first time the plugin runs it should report information
33 about all HAL devices found on the system. Every UDI provided
34 by HAL should be present in the devices list as is from HAL.
35 """
36 message = self.plugin.create_message()
37 actual_udis = [part[1][u"info.udi"] for part in message]
38 expected_udis = [device.udi for device
39 in self.hal_manager.get_devices()]
40 self.assertEqual(set(actual_udis), set(expected_udis))
41
42 def test_first_message(self):
43 """
44 The first time the plugin runs it should report information
45 about all HAL devices found on the system. All new devices
46 will be reported with 'create' actions.
47 """
48 message = self.plugin.create_message()
49 actions = [part[0] for part in message]
50 self.assertEqual(set(actions), set(["create"]))
51 self.assertSchema(message)
52
53 def test_no_changes(self):
54 """
55 Messages should not be created if hardware information is
56 unchanged since the last server exchange.
57 """
58 self.plugin.exchange()
59 self.assertNotEquals(len(self.mstore.get_pending_messages()), 0)
60
61 messages = self.mstore.get_pending_messages()
62 self.plugin.exchange()
63 self.assertEqual(self.mstore.get_pending_messages(), messages)
64
65 def test_update(self):
66 """
67 If a change is detected for a device that was previously
68 reported to the server, the changed device should be reported
69 with an 'update' action. Property changes are reported at a
70 key/value pair level.
71 """
72 self.hal_manager.devices = [
73 MockRealHALDevice({u"info.udi": u"wubble",
74 u"info.product": u"Wubble"})]
75 registry_mocker = self.mocker.replace(self.plugin.registry)
76 registry_mocker.flush()
77 self.mocker.count(2)
78 self.mocker.result(None)
79 self.mocker.replay()
80 message = self.plugin.create_message()
81 self.plugin.persist_data(None)
82 self.assertEqual(message, [("create", {u"info.udi": u"wubble",
83 u"info.product": u"Wubble"})])
84
85 self.hal_manager.devices[0] = MockRealHALDevice(
86 {u"info.udi": u"wubble", u"info.product": u"Ooga"})
87 message = self.plugin.create_message()
88 self.plugin.persist_data(None)
89 self.assertEqual(message, [("update", u"wubble",
90 {}, {u"info.product": u"Ooga"}, {})])
91 self.assertSchema(message)
92 self.assertEqual(self.plugin.create_message(), [])
93
94 def test_update_list(self):
95 """
96 An update should be sent to the server when a strlist device
97 property changes. No updates should be sent if a device is
98 unchanged.
99 """
100 self.hal_manager.devices = [
101 MockRealHALDevice({u"info.udi": u"wubble",
102 u"info.product": u"Wubble",
103 u"info.capabilities": [u"foo", u"bar"]})]
104
105 message = self.plugin.create_message()
106 self.plugin.persist_data(None)
107 self.assertEqual(message, [("create",
108 {u"info.udi": u"wubble",
109 u"info.product": u"Wubble",
110 u"info.capabilities": [u"foo", u"bar"]}),
111 ])
112
113 self.assertSchema(message)
114
115 self.hal_manager.devices[0] = MockRealHALDevice(
116 {u"info.udi": u"wubble", u"info.product": u"Wubble",
117 u"info.capabilities": [u"foo"]})
118 message = self.plugin.create_message()
119 self.plugin.persist_data(None)
120 self.assertEqual(message, [("update", u"wubble",
121 {}, {u"info.capabilities": [u"foo"]}, {}),
122 ])
123 self.assertSchema(message)
124
125 self.assertEqual(self.plugin.create_message(), [])
126
127 def test_update_complex(self):
128 """
129 The 'update' action reports property create, update and
130 delete changes.
131 """
132 self.hal_manager.devices = [
133 MockRealHALDevice({u"info.udi": u"wubble",
134 u"info.product": u"Wubble",
135 u"linux.acpi_type": 11})]
136
137 message = self.plugin.create_message()
138 self.plugin.persist_data(None)
139 self.assertEqual(message, [("create", {u"info.udi": u"wubble",
140 u"info.product": u"Wubble",
141 u"linux.acpi_type": 11})])
142
143 self.hal_manager.devices[0] = MockRealHALDevice(
144 {u"info.udi": u"wubble", u"info.product": u"Ooga",
145 u"info.category": u"unittest"})
146 message = self.plugin.create_message()
147 self.plugin.persist_data(None)
148 self.assertEqual(message, [("update", u"wubble",
149 {u"info.category": u"unittest"},
150 {u"info.product": u"Ooga"},
151 {u"linux.acpi_type": 11})])
152 self.assertSchema(message)
153
154 self.assertEqual(self.plugin.create_message(), [])
155
156 def test_delete(self):
157 """
158 If a device that was previously reported is no longer present
159 in a system a device entry should be created with a 'delete'
160 action.
161 """
162 self.hal_manager.devices = [
163 MockRealHALDevice({u"info.udi": u"wubble",
164 u"info.product": u"Wubble"}),
165 MockRealHALDevice({u"info.udi": u"ooga",
166 u"info.product": u"Ooga"})]
167
168 message = self.plugin.create_message()
169 self.plugin.persist_data(None)
170 self.assertEqual(message, [("create", {u"info.udi": u"wubble",
171 u"info.product": u"Wubble"}),
172 ("create", {u"info.udi": u"ooga",
173 u"info.product": u"Ooga"})])
174 self.assertSchema(message)
175
176 self.hal_manager.devices.pop(1)
177 message = self.plugin.create_message()
178 self.plugin.persist_data(None)
179 self.assertEqual(message, [("delete", u"ooga")])
180 self.assertSchema(message)
181 self.assertEqual(self.plugin.create_message(), [])
182
183 def test_minimal_delete(self):
184 self.hal_manager.devices = [
185 MockRealHALDevice({u"info.udi": u"wubble",
186 u"block.device": u"/dev/scd",
187 u"storage.removable": True}),
188 MockRealHALDevice({u"info.udi": u"wubble0",
189 u"block.device": u"/dev/scd0",
190 u"info.parent": u"wubble"}),
191 MockRealHALDevice({u"info.udi": u"wubble1",
192 u"block.device": u"/dev/scd1",
193 u"info.parent": u"wubble"}),
194 MockRealHALDevice({u"info.udi": u"wubble2",
195 u"block.device": u"/dev/scd1",
196 u"info.parent": u"wubble0"}),
197 MockRealHALDevice({u"info.udi": u"wubble3",
198 u"block.device": u"/dev/scd1",
199 u"info.parent": u"wubble2"})]
200
201 message = self.plugin.create_message()
202 self.plugin.persist_data(None)
203
204 del self.hal_manager.devices[:]
205
206 message = self.plugin.create_message()
207 self.plugin.persist_data(None)
208
209 self.assertEqual(message, [("delete", u"wubble")])
210 self.assertEqual(self.plugin.create_message(), [])
211
212 def test_resynchronize(self):
213 """
214 If a 'resynchronize' reactor event is fired, the plugin should
215 send a message that contains all data as if the server has
216 none.
217 """
218 self.plugin.exchange()
219 self.reactor.fire("resynchronize")
220 self.plugin.exchange()
221
222 messages = self.mstore.get_pending_messages()
223 self.assertEqual(len(messages), 2)
224 self.assertEqual(messages[0]["devices"], messages[1]["devices"])
225
226 def test_call_on_accepted(self):
227 remote_broker_mock = self.mocker.replace(self.remote)
228 remote_broker_mock.send_message(ANY, urgent=True)
229 self.mocker.result(succeed(None))
230 self.mocker.replay()
231
232 self.reactor.fire(("message-type-acceptance-changed",
233 "hardware-inventory"),
234 True)
235
236 def test_no_message_if_not_accepted(self):
237 """
238 Don't add any messages at all if the broker isn't currently
239 accepting their type.
240 """
241 self.mstore.set_accepted_types([])
242 self.reactor.advance(self.monitor.step_size * 2)
243 self.monitor.exchange()
244
245 self.mstore.set_accepted_types(["hardware-inventory"])
246 self.assertMessages(list(self.mstore.get_pending_messages()), [])
247
248 def test_do_not_persist_changes_when_send_message_fails(self):
249 """
250 When the plugin is run it persists data that it uses on
251 subsequent checks to calculate the delta to send. It should
252 only persist data when the broker confirms that the message
253 sent by the plugin has been sent.
254 """
255
256 class MyException(Exception):
257 pass
258
259 self.log_helper.ignore_errors(MyException)
260
261 broker_mock = self.mocker.replace(self.monitor.broker)
262 broker_mock.send_message(ANY, urgent=ANY)
263 self.mocker.result(fail(MyException()))
264 self.mocker.replay()
265
266 message = self.plugin.create_message()
267
268 def assert_message(message_id):
269 self.assertEqual(message, self.plugin.create_message())
270
271 result = self.plugin.exchange()
272 result.addCallback(assert_message)
273 return result
0274
=== modified file 'landscape/monitor/tests/test_mountinfo.py'
--- landscape/monitor/tests/test_mountinfo.py 2011-12-01 13:38:58 +0000
+++ landscape/monitor/tests/test_mountinfo.py 2012-03-06 09:20:24 +0000
@@ -3,6 +3,7 @@
3from twisted.internet.defer import succeed3from twisted.internet.defer import succeed
44
5from landscape.monitor.mountinfo import MountInfo5from landscape.monitor.mountinfo import MountInfo
6from landscape.tests.test_hal import MockHALManager, MockRealHALDevice
6from landscape.tests.helpers import LandscapeTest, mock_counter, MonitorHelper7from landscape.tests.helpers import LandscapeTest, mock_counter, MonitorHelper
7from landscape.tests.mocker import ANY8from landscape.tests.mocker import ANY
89
@@ -21,6 +22,8 @@
21 self.log_helper.ignore_errors("Typelib file for namespace")22 self.log_helper.ignore_errors("Typelib file for namespace")
2223
23 def get_mount_info(self, *args, **kwargs):24 def get_mount_info(self, *args, **kwargs):
25 hal_devices = kwargs.pop("hal_devices", [])
26 kwargs["hal_manager"] = MockHALManager(hal_devices)
24 if "statvfs" not in kwargs:27 if "statvfs" not in kwargs:
25 kwargs["statvfs"] = lambda path: (0,) * 1028 kwargs["statvfs"] = lambda path: (0,) * 10
26 return MountInfo(*args, **kwargs)29 return MountInfo(*args, **kwargs)
@@ -320,8 +323,51 @@
320 message = plugin.create_mount_info_message()323 message = plugin.create_mount_info_message()
321 self.assertEqual(message, None)324 self.assertEqual(message, None)
322325
326 def test_ignore_removable_partitions(self):
327 """
328 Partitions on removable devices don't directly report
329 storage.removable : True, but they do point to their parent and the
330 parent will be marked removable if appropriate.
331 """
332 devices = [MockRealHALDevice({"info.udi": "wubble",
333 "block.device": "/dev/scd",
334 "storage.removable": True}),
335 MockRealHALDevice({"info.udi": "wubble0",
336 "block.device": "/dev/scd0",
337 "info.parent": "wubble"})]
338
339 filename = self.makeFile("""\
340/dev/scd0 /media/Xerox_M750 iso9660 ro,nosuid,nodev,uid=1000,utf8 0 0
341""")
342 plugin = self.get_mount_info(mounts_file=filename, hal_devices=devices,
343 mtab_file=filename)
344 self.monitor.add(plugin)
345 plugin.run()
346
347 message = plugin.create_mount_info_message()
348 self.assertEqual(message, None)
349
323 def test_ignore_removable_devices(self):350 def test_ignore_removable_devices(self):
324 """351 """
352 The mount info plugin should only report data about
353 non-removable devices.
354 """
355 devices = [MockRealHALDevice({"info.udi": "wubble",
356 "block.device": "/dev/scd0",
357 "storage.removable": True})]
358 filename = self.makeFile("""\
359/dev/scd0 /media/Xerox_M750 iso9660 ro,nosuid,nodev,uid=1000,utf8 0 0
360""")
361 plugin = self.get_mount_info(mounts_file=filename, hal_devices=devices,
362 mtab_file=filename)
363 self.monitor.add(plugin)
364 plugin.run()
365
366 message = plugin.create_mount_info_message()
367 self.assertEqual(message, None)
368
369 def test_ignore_removable_devices_gudev(self):
370 """
325 The mount info plugin uses gudev to retrieve removable information371 The mount info plugin uses gudev to retrieve removable information
326 about devices.372 about devices.
327 """373 """
@@ -330,6 +376,7 @@
330""")376""")
331 plugin = self.get_mount_info(mounts_file=filename,377 plugin = self.get_mount_info(mounts_file=filename,
332 mtab_file=filename)378 mtab_file=filename)
379 plugin._hal_manager = None
333380
334 class MockDevice(object):381 class MockDevice(object):
335 def get_sysfs_attr_as_boolean(self, attr):382 def get_sysfs_attr_as_boolean(self, attr):
@@ -348,6 +395,35 @@
348 message = plugin.create_mount_info_message()395 message = plugin.create_mount_info_message()
349 self.assertEqual(message, None)396 self.assertEqual(message, None)
350397
398 def test_ignore_multiparented_removable_devices(self):
399 """
400 Some removable devices might be the grand-children of a device that is
401 marked as "storage.removable".
402 """
403 devices = [MockRealHALDevice({"info.udi": "wubble",
404 "block.device": "/dev/scd",
405 "storage.removable": True}),
406 MockRealHALDevice({"info.udi": "wubble0",
407 "block.device": "/dev/scd0",
408 "info.parent": "wubble"}),
409 MockRealHALDevice({"info.udi": "wubble0a",
410 "block.device": "/dev/scd0a",
411 "info.parent": "wubble0"}),
412 MockRealHALDevice({"info.udi": "wubble0b",
413 "block.device": "/dev/scd0b",
414 "info.parent": "wubble0"})]
415
416 filename = self.makeFile("""\
417/dev/scd0a /media/Xerox_M750 iso9660 ro,nosuid,nodev,uid=1000,utf8 0 0
418""")
419 plugin = self.get_mount_info(mounts_file=filename, hal_devices=devices,
420 mtab_file=filename)
421 self.monitor.add(plugin)
422 plugin.run()
423
424 message = plugin.create_mount_info_message()
425 self.assertEqual(message, None)
426
351 def test_sample_free_space(self):427 def test_sample_free_space(self):
352 """Test collecting information about free space."""428 """Test collecting information about free space."""
353 def statvfs(path, multiplier=mock_counter(1).next):429 def statvfs(path, multiplier=mock_counter(1).next):
354430
=== modified file 'landscape/monitor/tests/test_service.py'
--- landscape/monitor/tests/test_service.py 2011-12-02 08:35:25 +0000
+++ landscape/monitor/tests/test_service.py 2012-03-06 09:20:24 +0000
@@ -1,3 +1,4 @@
1from landscape.tests.mocker import ANY
1from landscape.tests.helpers import LandscapeTest, FakeBrokerServiceHelper2from landscape.tests.helpers import LandscapeTest, FakeBrokerServiceHelper
2from landscape.reactor import FakeReactor3from landscape.reactor import FakeReactor
3from landscape.monitor.config import MonitorConfiguration, ALL_PLUGINS4from landscape.monitor.config import MonitorConfiguration, ALL_PLUGINS
@@ -45,10 +46,15 @@
45 starts the plugins and register the monitor as broker client. It also46 starts the plugins and register the monitor as broker client. It also
46 start listening on its own socket for incoming connections.47 start listening on its own socket for incoming connections.
47 """48 """
49 # FIXME: don't actually run the real register method, because at the
50 # moment the UserMonitor plugin still depends on DBus. We can probably
51 # drop this mocking once the AMP migration is completed.
52 for plugin in self.service.plugins:
53 plugin.register = self.mocker.mock()
54 plugin.register(ANY)
55 self.mocker.replay()
56
48 def stop_service(ignored):57 def stop_service(ignored):
49 for plugin in self.service.plugins:
50 if getattr(plugin, "stop", None) is not None:
51 plugin.stop()
52 [connector] = self.broker_service.broker.get_connectors()58 [connector] = self.broker_service.broker.get_connectors()
53 connector.disconnect()59 connector.disconnect()
54 self.service.stopService()60 self.service.stopService()
5561
=== modified file 'landscape/package/releaseupgrader.py'
--- landscape/package/releaseupgrader.py 2011-12-01 14:01:29 +0000
+++ landscape/package/releaseupgrader.py 2012-03-06 09:20:24 +0000
@@ -188,11 +188,32 @@
188 config.add_section("NonInteractive")188 config.add_section("NonInteractive")
189 config.set("NonInteractive", "ForceOverwrite", "no")189 config.set("NonInteractive", "ForceOverwrite", "no")
190190
191 # Workaround for Bug #174148, which prevents dbus from restarting
192 # after a dapper->hardy upgrade
193 if not config.has_section("Distro"):
194 config.add_section("Distro")
195 if not config.has_option("Distro", "PostInstallScripts"):
196 config.set("Distro", "PostInstallScripts", "./dbus.sh")
197 else:
198 scripts = config.get("Distro", "PostInstallScripts")
199 scripts += ", ./dbus.sh"
200 config.set("Distro", "PostInstallScripts", scripts)
201
191 # Write config changes to disk202 # Write config changes to disk
192 fd = open(config_filename, "w")203 fd = open(config_filename, "w")
193 config.write(fd)204 config.write(fd)
194 fd.close()205 fd.close()
195206
207 # Generate the post-install script that starts DBus
208 dbus_sh_filename = os.path.join(upgrade_tool_directory,
209 "dbus.sh")
210 fd = open(dbus_sh_filename, "w")
211 fd.write("#!/bin/sh\n"
212 "/etc/init.d/dbus start\n"
213 "sleep 10\n")
214 fd.close()
215 os.chmod(dbus_sh_filename, 0755)
216
196 # On some releases the upgrade-tool doesn't support the allow third217 # On some releases the upgrade-tool doesn't support the allow third
197 # party environment variable, so this trick is needed to make it218 # party environment variable, so this trick is needed to make it
198 # possible to upgrade against testing client packages from the219 # possible to upgrade against testing client packages from the
199220
=== modified file 'landscape/package/tests/test_releaseupgrader.py'
--- landscape/package/tests/test_releaseupgrader.py 2011-12-01 14:01:29 +0000
+++ landscape/package/tests/test_releaseupgrader.py 2012-03-06 09:20:24 +0000
@@ -245,6 +245,60 @@
245 result.addCallback(check_result)245 result.addCallback(check_result)
246 return result246 return result
247247
248 def test_tweak_sets_dbus_start_script(self):
249 """
250 The L{ReleaseUpgrader.tweak} method adds to the upgrade-tool
251 configuration a little script that starts dbus after the upgrade.
252 """
253 config_filename = os.path.join(self.config.upgrade_tool_directory,
254 "DistUpgrade.cfg.dapper")
255 self.makeFile(path=config_filename,
256 content="[Distro]\n"
257 "PostInstallScripts=/foo.sh\n")
258
259 def check_result(ignored):
260 config = ConfigParser.ConfigParser()
261 config.read(config_filename)
262 self.assertEqual(config.get("Distro", "PostInstallScripts"),
263 "/foo.sh, ./dbus.sh")
264 dbus_sh = os.path.join(self.config.upgrade_tool_directory,
265 "dbus.sh")
266 self.assertFileContent(dbus_sh,
267 "#!/bin/sh\n"
268 "/etc/init.d/dbus start\n"
269 "sleep 10\n")
270
271 result = self.upgrader.tweak("dapper")
272 result.addCallback(check_result)
273 return result
274
275 def test_tweak_sets_dbus_start_script_with_no_post_install_scripts(self):
276 """
277 The L{ReleaseUpgrader.tweak} method adds to the upgrade-tool
278 configuration a little script that starts dbus after the upgrade. This
279 works even when the config file doesn't have a PostInstallScripts entry
280 yet.
281 """
282 config_filename = os.path.join(self.config.upgrade_tool_directory,
283 "DistUpgrade.cfg.dapper")
284 self.makeFile(path=config_filename, content="")
285
286 def check_result(ignored):
287 config = ConfigParser.ConfigParser()
288 config.read(config_filename)
289 self.assertEqual(config.get("Distro", "PostInstallScripts"),
290 "./dbus.sh")
291 dbus_sh = os.path.join(self.config.upgrade_tool_directory,
292 "dbus.sh")
293 self.assertFileContent(dbus_sh,
294 "#!/bin/sh\n"
295 "/etc/init.d/dbus start\n"
296 "sleep 10\n")
297
298 result = self.upgrader.tweak("dapper")
299 result.addCallback(check_result)
300 return result
301
248 def test_default_logs_directory(self):302 def test_default_logs_directory(self):
249 """303 """
250 The default directory for the upgrade-tool logs is the system one.304 The default directory for the upgrade-tool logs is the system one.
251305
=== modified file 'landscape/reactor.py'
--- landscape/reactor.py 2012-02-03 18:10:57 +0000
+++ landscape/reactor.py 2012-03-06 09:20:24 +0000
@@ -16,6 +16,10 @@
16 """Raised when an invalid ID is used with reactor.cancel_call()."""16 """Raised when an invalid ID is used with reactor.cancel_call()."""
1717
1818
19class CallHookError(Exception):
20 """Raised when hooking on a reactor incorrectly."""
21
22
19class EventID(object):23class EventID(object):
20 """Unique identifier for an event handler.24 """Unique identifier for an event handler.
2125
@@ -155,6 +159,13 @@
155 except Exception, e:159 except Exception, e:
156 logging.exception(e)160 logging.exception(e)
157161
162 def _hook_threaded_callbacks(self):
163 id = self.call_every(0.5, self._run_threaded_callbacks)
164 self._run_threaded_callbacks_id = id
165
166 def _unhook_threaded_callbacks(self):
167 self.cancel_call(self._run_threaded_callbacks_id)
168
158169
159class UnixReactorMixin(object):170class UnixReactorMixin(object):
160171
161172
=== modified file 'landscape/service.py'
--- landscape/service.py 2012-02-03 10:06:28 +0000
+++ landscape/service.py 2012-03-06 09:20:24 +0000
@@ -29,6 +29,12 @@
2929
30 def __init__(self, config):30 def __init__(self, config):
31 self.config = config31 self.config = config
32 try:
33 from landscape.lib import bpickle_dbus
34 except ImportError:
35 pass
36 else:
37 bpickle_dbus.install()
32 self.reactor = self.reactor_factory()38 self.reactor = self.reactor_factory()
33 if self.persist_filename:39 if self.persist_filename:
34 self.persist = get_versioned_persist(self)40 self.persist = get_versioned_persist(self)
3541
=== modified file 'landscape/tests/test_configuration.py'
--- landscape/tests/test_configuration.py 2012-02-29 22:13:01 +0000
+++ landscape/tests/test_configuration.py 2012-03-06 09:20:24 +0000
@@ -1883,7 +1883,7 @@
18831883
1884 def test_register_bus_connection_failure_ok_no_register(self):1884 def test_register_bus_connection_failure_ok_no_register(self):
1885 """1885 """
1886 Exit code 0 will be returned if we can't contact Landscape and1886 Exit code 0 will be returned if we can't contact Landscape via DBus and
1887 --ok-no-register was passed.1887 --ok-no-register was passed.
1888 """1888 """
1889 print_text_mock = self.mocker.replace(print_text)1889 print_text_mock = self.mocker.replace(print_text)
18901890
=== added file 'landscape/tests/test_hal.py'
--- landscape/tests/test_hal.py 1970-01-01 00:00:00 +0000
+++ landscape/tests/test_hal.py 2012-03-06 09:20:24 +0000
@@ -0,0 +1,87 @@
1from dbus import SystemBus, Interface
2from dbus.exceptions import DBusException
3
4from landscape.hal import HALDevice, HALManager
5from landscape.tests.helpers import LandscapeTest
6
7
8class HALManagerTest(LandscapeTest):
9
10 def setUp(self):
11 super(HALManagerTest, self).setUp()
12 self.bus = SystemBus()
13
14 def test_get_devices(self):
15 """
16 A HALManager can return a flat list of devices. All available
17 devices should be included in the returned list.
18 """
19 devices = HALManager().get_devices()
20 manager = self.bus.get_object("org.freedesktop.Hal",
21 "/org/freedesktop/Hal/Manager")
22 manager = Interface(manager, "org.freedesktop.Hal.Manager")
23 expected_devices = manager.GetAllDevices()
24 actual_devices = [device.udi for device in devices]
25 self.assertEqual(set(expected_devices), set(actual_devices))
26
27 def test_get_devices_with_dbus_error(self):
28 """
29 If the L{HALManager} fails connecting to HAL over D-Bus, then the
30 L{HALManager.get_devices} method returns an empty list.
31 """
32 self.log_helper.ignore_errors("Couldn't connect to Hal via DBus")
33 bus = self.mocker.mock()
34 bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager")
35 self.mocker.throw(DBusException())
36 self.mocker.replay()
37 devices = HALManager(bus=bus).get_devices()
38 self.assertEqual(devices, [])
39
40 def test_get_devices_with_no_server(self):
41 """
42 If the L{HALManager} fails connecting to HAL over D-Bus, for example
43 because the DBus server is not running at all, then the
44 L{HALManager.get_devices} method returns an empty list.
45 """
46 self.log_helper.ignore_errors("Couldn't connect to Hal via DBus")
47 bus_mock = self.mocker.replace("dbus.SystemBus")
48 bus_mock()
49 self.mocker.throw(DBusException())
50 self.mocker.replay()
51 devices = HALManager().get_devices()
52 self.assertEqual(devices, [])
53
54
55class MockHALManager(object):
56
57 def __init__(self, devices):
58 self.devices = devices
59
60 def get_devices(self):
61 return [HALDevice(device) for device in self.devices]
62
63
64class MockRealHALDevice(object):
65
66 def __init__(self, properties):
67 self._properties = properties
68 self.udi = properties.get("info.udi", "fake_udi")
69
70 def GetAllProperties(self):
71 return self._properties
72
73
74class HALDeviceTest(LandscapeTest):
75
76 def test_init(self):
77 device = HALDevice(MockRealHALDevice({"info.udi": "wubble"}))
78 self.assertEqual(device.properties, {"info.udi": "wubble"})
79 self.assertEqual(device.udi, "wubble")
80 self.assertEqual(device.parent, None)
81
82 def test_add_child(self):
83 parent = HALDevice(MockRealHALDevice({"info.udi": "wubble"}))
84 child = HALDevice(MockRealHALDevice({"info.udi": "ooga"}))
85 parent.add_child(child)
86 self.assertEqual(parent.get_children(), [child])
87 self.assertEqual(child.parent, parent)
088
=== modified file 'landscape/tests/test_service.py'
--- landscape/tests/test_service.py 2011-12-01 13:38:58 +0000
+++ landscape/tests/test_service.py 2012-03-06 09:20:24 +0000
@@ -56,6 +56,15 @@
56 service = TestService(self.config)56 service = TestService(self.config)
57 self.assertFalse(hasattr(service, "persist"))57 self.assertFalse(hasattr(service, "persist"))
5858
59 def test_install_bpickle_dbus(self):
60 """
61 A L{LandscapeService} installs the DBus extensions of bpickle.
62 """
63 dbus_mock = self.mocker.replace("landscape.lib.bpickle_dbus.install")
64 dbus_mock()
65 self.mocker.replay()
66 TestService(self.config)
67
59 def test_usr1_rotates_logs(self):68 def test_usr1_rotates_logs(self):
60 """69 """
61 SIGUSR1 should cause logs to be reopened.70 SIGUSR1 should cause logs to be reopened.
6271
=== modified file 'landscape/tests/test_textmessage.py'
--- landscape/tests/test_textmessage.py 2011-12-01 14:01:29 +0000
+++ landscape/tests/test_textmessage.py 2012-03-06 09:20:24 +0000
@@ -13,8 +13,8 @@
1313
14 def test_send_message(self):14 def test_send_message(self):
15 """15 """
16 L{send_message} should send a message of type C{text-message} to the16 L{send_message} should send a message of type
17 landscape messaging service.17 C{text-message} to the landscape dbus messaging service.
18 """18 """
19 service = self.broker_service19 service = self.broker_service
20 service.message_store.set_accepted_types(["text-message"])20 service.message_store.set_accepted_types(["text-message"])
2121
=== modified file 'landscape/textmessage.py'
--- landscape/textmessage.py 2011-12-01 14:01:29 +0000
+++ landscape/textmessage.py 2012-03-06 09:20:24 +0000
@@ -1,6 +1,7 @@
1"""1"""
2Support code for the C{landscape-message} utility, which sends a text2Support code for the C{landscape-message} utility, which sends a text
3message to the Landscape web UI via the landscape-client's messaging service.3message to the Landscape web UI via the landscape-client's dbus
4messaging service (see L{landscape.plugins.dbus_message}).
4"""5"""
56
6import sys7import sys
78
=== modified file 'landscape/watchdog.py'
--- landscape/watchdog.py 2012-02-27 20:49:50 +0000
+++ landscape/watchdog.py 2012-03-06 09:20:24 +0000
@@ -62,6 +62,8 @@
62 @cvar program: The name of the executable program that will start this62 @cvar program: The name of the executable program that will start this
63 daemon.63 daemon.
64 @cvar username: The name of the user to switch to, by default.64 @cvar username: The name of the user to switch to, by default.
65 @cvar service: The DBus service name that the program will be expected to
66 listen on.
65 @cvar max_retries: The maximum number of retries before giving up when67 @cvar max_retries: The maximum number of retries before giving up when
66 trying to connect to the watched daemon.68 trying to connect to the watched daemon.
67 @cvar factor: The factor by which the delay between subsequent connection69 @cvar factor: The factor by which the delay between subsequent connection
@@ -176,7 +178,8 @@
176178
177 def is_running(self):179 def is_running(self):
178 # FIXME Error cases may not be handled in the best possible way180 # FIXME Error cases may not be handled in the best possible way
179 # here. We're basically return False if any error happens.181 # here. We're basically return False if any error happens from the
182 # dbus ping.
180 return self._connect_and_call("ping")183 return self._connect_and_call("ping")
181184
182 def wait(self):185 def wait(self):
@@ -364,7 +367,7 @@
364 def start(self):367 def start(self):
365 """368 """
366 Start all daemons. The broker will be started first, and no other369 Start all daemons. The broker will be started first, and no other
367 daemons will be started before it is running and responding to370 daemons will be started before it is running and responding to DBUS
368 messages.371 messages.
369372
370 @return: A deferred which fires when all services have successfully373 @return: A deferred which fires when all services have successfully
371374
=== modified file 'man/landscape-client.1'
--- man/landscape-client.1 2011-12-02 09:52:48 +0000
+++ man/landscape-client.1 2012-03-06 09:20:24 +0000
@@ -1,5 +1,5 @@
1.\"Text automatically generated by txt2man1.\"Text automatically generated by txt2man
2.TH landscape-client 1 "02 December 2011" "" ""2.TH landscape-client "18 January 2010" "" ""
3.SH NAME3.SH NAME
4\fBlandscape-client \fP- Landscape system client4\fBlandscape-client \fP- Landscape system client
5\fB5\fB
@@ -35,6 +35,11 @@
35\fIoptions\fP override settings from the file).35\fIoptions\fP override settings from the file).
36.TP36.TP
37.B37.B
38\fB--bus\fP=BUS
39Which DBUS bus to use. One of 'session' or
40'system'.
41.TP
42.B
38\fB-d\fP PATH, \fB--data-path\fP=PATH43\fB-d\fP PATH, \fB--data-path\fP=PATH
39The directory to store data files in.44The directory to store data files in.
40.TP45.TP
4146
=== modified file 'man/landscape-client.txt'
--- man/landscape-client.txt 2011-12-01 14:01:29 +0000
+++ man/landscape-client.txt 2012-03-06 09:20:24 +0000
@@ -17,6 +17,8 @@
17 -h, --help Show this help message and exit.17 -h, --help Show this help message and exit.
18 -c FILE, --config=FILE Use config from this file (any command line 18 -c FILE, --config=FILE Use config from this file (any command line
19 options override settings from the file).19 options override settings from the file).
20 --bus=BUS Which DBUS bus to use. One of 'session' or
21 'system'.
20 -d PATH, --data-path=PATH The directory to store data files in.22 -d PATH, --data-path=PATH The directory to store data files in.
21 -q, --quiet Do not log to the standard output.23 -q, --quiet Do not log to the standard output.
22 -l FILE, --log-dir=FILE The directory to write log files to.24 -l FILE, --log-dir=FILE The directory to write log files to.
2325
=== modified file 'man/landscape-config.1'
--- man/landscape-config.1 2011-12-02 09:52:48 +0000
+++ man/landscape-config.1 2012-03-06 09:20:24 +0000
@@ -1,5 +1,5 @@
1.\"Text automatically generated by txt2man1.\"Text automatically generated by txt2man
2.TH landscape-config 1 "02 December 2011" "" ""2.TH landscape-config "18 January 2010" "" ""
3.SH NAME3.SH NAME
4\fBlandscape-config \fP- configure the Landscape management client4\fBlandscape-config \fP- configure the Landscape management client
5\fB5\fB
@@ -46,6 +46,11 @@
46'/etc/landscape/client.conf').46'/etc/landscape/client.conf').
47.TP47.TP
48.B48.B
49\fB--bus\fP=BUS
50Which DBUS bus to use. One of 'session' or 'system'
51(default: 'system').
52.TP
53.B
49\fB-d\fP PATH, \fB--data-path\fP=PATH54\fB-d\fP PATH, \fB--data-path\fP=PATH
50The directory to store data files in (default:55The directory to store data files in (default:
51'/var/lib/landscape/client').56'/var/lib/landscape/client').
5257
=== modified file 'man/landscape-config.txt'
--- man/landscape-config.txt 2011-12-01 14:01:29 +0000
+++ man/landscape-config.txt 2012-03-06 09:20:24 +0000
@@ -28,6 +28,8 @@
28 -c FILE, --config=FILE Use config from this file (any command line options28 -c FILE, --config=FILE Use config from this file (any command line options
29 override settings from the file) (default:29 override settings from the file) (default:
30 '/etc/landscape/client.conf').30 '/etc/landscape/client.conf').
31 --bus=BUS Which DBUS bus to use. One of 'session' or 'system'
32 (default: 'system').
31 -d PATH, --data-path=PATH The directory to store data files in (default:33 -d PATH, --data-path=PATH The directory to store data files in (default:
32 '/var/lib/landscape/client').34 '/var/lib/landscape/client').
33 -q, --quiet Do not log to the standard output.35 -q, --quiet Do not log to the standard output.
3436
=== modified file 'man/landscape-message.1'
--- man/landscape-message.1 2011-12-02 09:52:48 +0000
+++ man/landscape-message.1 2012-03-06 09:20:24 +0000
@@ -1,5 +1,5 @@
1.\"Text automatically generated by txt2man1.\"Text automatically generated by txt2man
2.TH landscape-message 1 "02 December 2011" "" ""2.TH landscape-message "18 January 2010" "" ""
3.SH NAME3.SH NAME
4\fBlandscape-message \fP- Send a message to the landscape web interface4\fBlandscape-message \fP- Send a message to the landscape web interface
5\fB5\fB
@@ -31,6 +31,10 @@
31.B31.B
32\fB-h\fP, \fB--help\fP32\fB-h\fP, \fB--help\fP
33Show this help message and exit.33Show this help message and exit.
34.TP
35.B
36\fB-b\fP BUS, \fB--bus\fP=BUS
37The DBUS bus to use to send the message.
34.SH EXAMPLES38.SH EXAMPLES
3539
36\fBlandscape-message\fP Hello administrator40\fBlandscape-message\fP Hello administrator
3741
=== modified file 'man/landscape-message.txt'
--- man/landscape-message.txt 2011-12-01 14:01:29 +0000
+++ man/landscape-message.txt 2012-03-06 09:20:24 +0000
@@ -16,6 +16,7 @@
16OPTIONS16OPTIONS
17 --version Show program's version number and exit.17 --version Show program's version number and exit.
18 -h, --help Show this help message and exit.18 -h, --help Show this help message and exit.
19 -b BUS, --bus=BUS The DBUS bus to use to send the message.
1920
20EXAMPLES21EXAMPLES
2122
2223
=== added file 'scripts/landscape-dbus-proxy'
--- scripts/landscape-dbus-proxy 1970-01-01 00:00:00 +0000
+++ scripts/landscape-dbus-proxy 2012-03-06 09:20:24 +0000
@@ -0,0 +1,82 @@
1#!/usr/bin/env python
2
3import os
4import dbus
5import dbus.service
6import dbus.glib # This as side effects, don't remove it!
7
8from dbus.service import Object, BusName, method
9
10from twisted.internet import glib2reactor
11glib2reactor.install()
12from twisted.internet import reactor
13
14from landscape.lib.bpickle import loads
15from landscape.lib.lock import lock_path, LockError
16from landscape.reactor import TwistedReactor
17from landscape.deployment import Configuration
18from landscape.broker.amp import RemoteBrokerConnector
19
20
21BUS_NAME = "com.canonical.landscape.Broker"
22OBJECT_PATH = "/com/canonical/landscape/Broker"
23
24
25def array_to_string(array):
26 """Convert an L{Array} of L{Byte}s (or integers) to a Python str."""
27 result = []
28 for item in array:
29 if item < 0:
30 item = item + 256
31 result.append(chr(item))
32 return "".join(result)
33
34
35class BrokerDBusObject(Object):
36 """A DBus-published object proxying L{RemoteBroker.send_message}.
37
38 It is used when upgrading from a DBus-based version of the Landscape client
39 to the newer AMP-based one, for letting the old package-changer process
40 performing the upgrade communicate with the new version of the client.
41 """
42
43 bus_name = BUS_NAME
44 object_path = OBJECT_PATH
45
46 def __init__(self, config):
47 super(BrokerDBusObject, self).__init__(BusName(
48 self.bus_name, dbus.SystemBus()), object_path=self.object_path)
49 self.config = config
50
51 @method(BUS_NAME)
52 def send_message(self, message, urgent=True):
53 """Queue the given message in the message exchange."""
54 message = loads(array_to_string(message))
55
56 def cb_connected(broker):
57 result = broker.send_message(message, urgent=True)
58 return result.addCallback(cb_done)
59
60 def cb_done(ignored):
61 return reactor.stop()
62
63 twisted_reactor = TwistedReactor()
64 connector = RemoteBrokerConnector(twisted_reactor, self.config)
65 connected = connector.connect()
66 connected.addCallback(cb_connected)
67
68
69if __name__ == "__main__":
70 config = Configuration()
71 lock_dir = os.path.join(config.data_path, "package")
72 if os.path.isdir(lock_dir):
73 lock_filename = os.path.join(lock_dir, "changer.lock")
74 try:
75 lock_path(lock_filename)
76 except LockError:
77 # The package-changer is running, this means that we're upgrading from
78 # a non-AMP version and that the upgrade is Landscape driven, so let's
79 # expose the DBus broker proxy to give a chance to the package-changer
80 # to send its result message.
81 remote = BrokerDBusObject(config)
82 reactor.run()
083
=== modified file 'setup.py'
--- setup.py 2012-03-05 15:22:36 +0000
+++ setup.py 2012-03-06 09:20:24 +0000
@@ -30,7 +30,8 @@
30 "scripts/landscape-package-reporter",30 "scripts/landscape-package-reporter",
31 "scripts/landscape-release-upgrader",31 "scripts/landscape-release-upgrader",
32 "scripts/landscape-sysinfo",32 "scripts/landscape-sysinfo",
33 "scripts/landscape-is-cloud-managed"],33 "scripts/landscape-is-cloud-managed",
34 "scripts/landscape-dbus-proxy"],
34 ext_modules=[Extension("landscape.lib.initgroups",35 ext_modules=[Extension("landscape.lib.initgroups",
35 ["landscape/lib/initgroups.c"])]36 ["landscape/lib/initgroups.c"])]
36 )37 )

Subscribers

People subscribed via source and target branches

to all changes: