4
from landscape.lib.disk import get_mount_info
5
from landscape.lib.monitor import CoverageMonitor
6
from landscape.accumulate import Accumulator
7
from landscape.hal import HALManager
8
from landscape.monitor.monitor import MonitorPlugin
11
class MountInfo(MonitorPlugin):
13
persist_name = "mount-info"
15
def __init__(self, interval=300, monitor_interval=60*60,
16
mounts_file="/proc/mounts", create_time=time.time,
17
statvfs=None, hal_manager=None, mtab_file="/etc/mtab"):
18
self.run_interval = interval
19
self._monitor_interval = monitor_interval
20
self._create_time = create_time
21
self._mounts_file = mounts_file
22
self._mtab_file = mtab_file
25
self._statvfs = statvfs
26
self._create_time = create_time
29
self._mount_activity = []
30
self._prev_mount_activity = {}
31
self._hal_manager = hal_manager or HALManager()
33
def register(self, registry):
34
super(MountInfo, self).register(registry)
35
self._accumulate = Accumulator(self._persist, self.registry.step_size)
36
self._monitor = CoverageMonitor(self.run_interval, 0.8,
37
"mount info snapshot",
38
create_time=self._create_time)
39
self.registry.reactor.call_every(self._monitor_interval,
41
self.registry.reactor.call_on("stop", self._monitor.log, priority=2000)
42
self.registry.reactor.call_on("resynchronize", self._resynchronize)
43
self.call_on_accepted("mount-info", self.send_messages, True)
45
def _resynchronize(self):
46
self.registry.persist.remove(self.persist_name)
48
def create_messages(self):
49
return filter(None, [self.create_mount_info_message(),
50
self.create_free_space_message(),
51
self.create_mount_activity_message()])
53
def create_mount_activity_message(self):
54
if self._mount_activity:
55
message = {"type": "mount-activity",
56
"activities": self._mount_activity}
57
self._mount_activity = []
61
def create_mount_info_message(self):
63
message = {"type": "mount-info", "mount-info": self._mount_info}
68
def create_free_space_message(self):
70
message = {"type": "free-space", "free-space": self._free_space}
75
def send_messages(self, urgent=False):
76
for message in self.create_messages():
77
self.registry.broker.send_message(message, urgent=urgent)
80
self.registry.broker.call_if_accepted("mount-info",
85
now = int(self._create_time())
86
current_mount_points = set()
87
for mount_info in self._get_mount_info():
88
mount_point = mount_info["mount-point"]
89
free_space = mount_info.pop("free-space")
91
key = ("accumulate-free-space", mount_point)
92
step_data = self._accumulate(now, free_space, key)
94
timestamp = step_data[0]
95
free_space = int(step_data[1])
96
self._free_space.append((timestamp, mount_point, free_space))
98
prev_mount_info = self._persist.get(("mount-info", mount_point))
99
if not prev_mount_info or prev_mount_info != mount_info:
100
self._persist.set(("mount-info", mount_point), mount_info)
101
self._mount_info.append((now, mount_info))
103
if not self._prev_mount_activity.get(mount_point, False):
104
self._mount_activity.append((now, mount_point, True))
105
self._prev_mount_activity[mount_point] = True
107
current_mount_points.add(mount_point)
109
for mount_point in self._prev_mount_activity:
110
if mount_point not in current_mount_points:
111
self._mount_activity.append((now, mount_point, False))
112
self._prev_mount_activity[mount_point] = False
114
def _get_removable_devices(self):
115
block_devices = {} # {udi: [device, ...]}
116
children = {} # {parent_udi: [child_udi, ...]}
119
# We walk the list of devices building up a dictionary of all removable
120
# devices, and a mapping of {UDI => [block devices]}
121
# We differentiate between devices that we definitely know are
122
# removable and devices that _may_ be removable, depending on their
123
# parent device, e.g. /dev/sdb1 isn't flagged as removable, but
124
# /dev/sdb may well be removable.
126
# Unfortunately, HAL doesn't guarantee the order of the devices
127
# returned from get_devices(), so we may not know that a parent device
128
# is removable when we find it's first child.
129
devices = self._hal_manager.get_devices()
130
for device in devices:
131
block_device = device.properties.get("block.device")
133
if device.properties.get("storage.removable"):
134
removable.add(device.udi)
137
block_devices[device.udi].append(block_device)
139
block_devices[device.udi] = [block_device]
141
parent_udi = device.properties.get("info.parent")
142
if parent_udi is not None:
144
children[parent_udi].append(device.udi)
146
children[parent_udi] = [device.udi]
148
# Propagate the removable flag from each node all the way to
153
for parent_udi in children:
154
if parent_udi in removable:
155
for child_udi in children[parent_udi]:
156
if child_udi not in removable:
157
removable.add(child_udi)
160
# We've now seen _all_ devices, and have the definitive list of
161
# removable UDIs, so we can now find all the removable devices in the
163
removable_devices = set()
164
for udi in removable:
165
removable_devices.update(block_devices[udi])
167
return removable_devices
169
def _get_mount_info(self):
170
"""Generator yields local mount points worth recording data for."""
171
file = open(self._mounts_file, "r")
172
removable_devices = self._get_removable_devices()
173
bound_mount_points = self._get_bound_mount_points()
175
for info in get_mount_info(self._mounts_file, self._statvfs):
176
device = info["device"]
177
mount_point = info["mount-point"]
178
if (device.startswith("/dev/") and
179
not mount_point.startswith("/dev/") and
180
not device in removable_devices and
181
not mount_point in bound_mount_points):
184
def _get_bound_mount_points(self):
186
Returns a set of mount points that have the "bind" option
187
by parsing /etc/mtab.
190
if not self._mtab_file or not os.path.isfile(self._mtab_file):
193
file = open(self._mtab_file, "r")
196
device, mount_point, filesystem, options = line.split()[:4]
197
mount_point = mount_point.decode("string-escape")
200
if "bind" in options.split(","):
201
bound_points.add(mount_point)