Merge lp:~ack/landscape-client/swift-usage into lp:~landscape/landscape-client/trunk

Proposed by Alberto Donato on 2014-05-16
Status: Merged
Approved by: Alberto Donato on 2014-05-25
Approved revision: 784
Merged at revision: 765
Proposed branch: lp:~ack/landscape-client/swift-usage
Merge into: lp:~landscape/landscape-client/trunk
Prerequisite: lp:~ack/landscape-client/ceph-usage-report-bytes
Diff against target: 963 lines (+354/-504)
4 files modified
landscape/message_schemas.py (+5/-1)
landscape/monitor/config.py (+1/-1)
landscape/monitor/swiftusage.py (+129/-158)
landscape/monitor/tests/test_swiftusage.py (+219/-344)
To merge this branch: bzr merge lp:~ack/landscape-client/swift-usage
Reviewer Review Type Date Requested Status
Chris Glass (community) 2014-05-16 Approve on 2014-05-23
Geoff Teale (community) 2014-05-16 Approve on 2014-05-20
Review via email: mp+219845@code.launchpad.net

Commit message

This branch replaces the SwiftDeviceInfo plugin with SwiftUsage, which reports free/used/available space for each device in the cluster.
It uses a different message type from the previous plugin.

Description of the change

This branch replaces the SwiftDeviceInfo plugin with SwiftUsage, which reports free/used/available space for each device in the cluster.
It uses a different message type from the previous plugin.

To post a comment you must log in.
lp:~ack/landscape-client/swift-usage updated on 2014-05-19
778. By Alberto Donato on 2014-05-19

Merged ceph-usage-report-bytes into swift-usage.

779. By Alberto Donato on 2014-05-19

Report usages as bytes, with integer type.

780. By Alberto Donato on 2014-05-19

Merge from trunk.

Geoff Teale (tealeg) wrote :

+1

[0]
landscape/monitor/swiftusage.py - Line 23

    This only works if the client runs on a Swift note. It requires the

Typo - "note" -> "node"

[1]
landscape/monitor/swiftusage.py - Line 47

            self.run_interval, 0.8, "Swift devices usage snapshot",

I'd say "Swift device usage" rather than "Swift devices usage"

review: Approve
Free Ekanayaka (free.ekanayaka) wrote :

Quick comment:

+SWIFT = Message("swift", {
+ "usages": List(Tuple(Int(), Unicode(), Int(), Int(), Int()))})
+

I'd rename this to SWIFT_USAGE/"swift-usage" for consistency with the Ceph one. Also, although I see it's used in other spots, I believe the plural "usages" is not quite correct in this context. A better term would be probably "data-points", but I see it's not the current pattern.

Please add a comment explaining the format of the message too, e.g.:

# Report Swift node usage in the last time interval.
SWIFT = Message("swift", {
    # List of data points of the form (timestamp, device, size, avail, used)
    "usages": List(Tuple(Int(), Unicode(), Int(), Int(), Int()))})

Free Ekanayaka (free.ekanayaka) wrote :

Uhm looking at the other branch it seems you're changing "ceph-usage" to "ceph". FWIW I prefer "ceph-usage", you could just bump the client API and change the format (e.g. rename "ceph-usages" to "data-points"). In case we have other Ceph-related messages they should be probably be separate (e.g. "ceph-otherstuff").

lp:~ack/landscape-client/swift-usage updated on 2014-05-21
781. By Alberto Donato on 2014-05-20

Change plugin interval.

782. By Alberto Donato on 2014-05-20

Address Geoff's review points.

783. By Alberto Donato on 2014-05-20

Rename swift message to swift-usage.

784. By Alberto Donato on 2014-05-21

Fix docstring.

Chris Glass (tribaal) wrote :

Looks good! +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'landscape/message_schemas.py'
2--- landscape/message_schemas.py 2014-05-19 07:45:24 +0000
3+++ landscape/message_schemas.py 2014-05-21 06:46:43 +0000
4@@ -136,6 +136,10 @@
5 KeyDict({"device": Unicode(), "mounted": Bool()}))
6 })
7
8+SWIFT_USAGE = Message("swift-usage", {
9+ # Usage data points in the form (timestamp, device, size, avail, used)
10+ "data-points": List(Tuple(Int(), Unicode(), Int(), Int(), Int()))})
11+
12 KEYSTONE_TOKEN = Message("keystone-token", {
13 "data": Any(Bytes(), Constant(None))
14 })
15@@ -470,5 +474,5 @@
16 EUCALYPTUS_INFO_ERROR, NETWORK_DEVICE, NETWORK_ACTIVITY,
17 REBOOT_REQUIRED_INFO, UPDATE_MANAGER_INFO, CPU_USAGE,
18 CEPH_USAGE, CEPH, SWIFT_DEVICE_INFO, KEYSTONE_TOKEN,
19- CHANGE_HA_SERVICE, JUJU_INFO, CLOUD_METADATA]:
20+ CHANGE_HA_SERVICE, JUJU_INFO, CLOUD_METADATA, SWIFT_USAGE]:
21 message_schemas[schema.type] = schema
22
23=== modified file 'landscape/monitor/config.py'
24--- landscape/monitor/config.py 2013-09-26 16:12:39 +0000
25+++ landscape/monitor/config.py 2014-05-21 06:46:43 +0000
26@@ -5,7 +5,7 @@
27 "LoadAverage", "MemoryInfo", "MountInfo", "ProcessorInfo",
28 "Temperature", "PackageMonitor", "UserMonitor",
29 "RebootRequired", "AptPreferences", "NetworkActivity",
30- "NetworkDevice", "UpdateManager", "CPUUsage", "SwiftDeviceInfo",
31+ "NetworkDevice", "UpdateManager", "CPUUsage", "SwiftUsage",
32 "CephUsage", "JujuInfo"]
33
34
35
36=== renamed file 'landscape/monitor/swiftdeviceinfo.py' => 'landscape/monitor/swiftusage.py'
37--- landscape/monitor/swiftdeviceinfo.py 2013-07-08 14:31:52 +0000
38+++ landscape/monitor/swiftusage.py 2014-05-21 06:46:43 +0000
39@@ -1,183 +1,154 @@
40 import logging
41 import time
42 import os
43-import json
44-
45-from landscape.lib.fetch import fetch, HTTPCodeError, PyCurlError, FetchError
46+
47+from twisted.internet import threads
48+
49+from landscape.accumulate import Accumulator
50 from landscape.lib.monitor import CoverageMonitor
51 from landscape.lib.network import get_active_device_info
52 from landscape.monitor.plugin import MonitorPlugin
53
54-
55-class SwiftDeviceInfo(MonitorPlugin):
56-
57- persist_name = "swift-device-info"
58+try:
59+ from swift.common.ring import Ring
60+ from swift.cli.recon import Scout
61+ has_swift = True
62+except ImportError:
63+ has_swift = False
64+
65+
66+class SwiftUsage(MonitorPlugin):
67+ """Plugin reporting Swift cluster usage.
68+
69+ This only works if the client runs on a Swift node. It requires the
70+ 'python-swift' package to be installed (which is installed on swift nodes).
71+
72+ """
73+
74+ persist_name = "swift-usage"
75 scope = "storage"
76
77- def __init__(self, interval=300, monitor_interval=60 * 60,
78+ def __init__(self, interval=30, monitor_interval=60 * 60,
79 create_time=time.time,
80- swift_config="/etc/swift/object-server.conf",
81 swift_ring="/etc/swift/object.ring.gz"):
82- self.run_interval = interval
83+ self._interval = interval
84 self._monitor_interval = monitor_interval
85 self._create_time = create_time
86- self._fetch = fetch
87- self._get_network_devices = get_active_device_info
88- self._swift_config = swift_config # If exists, we are a swift node
89- self._swift_ring = swift_ring # To discover swift recon port
90- self._swift_recon_url = None
91- self._create_time = create_time
92- self._swift_device_info = []
93- self._swift_device_info_to_persist = []
94- self.enabled = True
95+ self._swift_ring = swift_ring # To discover Recon host/port
96+
97+ self._has_swift = has_swift
98+ self._swift_usage_points = []
99+ self.active = True
100
101 def register(self, registry):
102- super(SwiftDeviceInfo, self).register(registry)
103- self._monitor = CoverageMonitor(self.run_interval, 0.8,
104- "swift device info snapshot",
105- create_time=self._create_time)
106- self.registry.reactor.call_every(self._monitor_interval,
107- self._monitor.log)
108+ super(SwiftUsage, self).register(registry)
109+ self._accumulate = Accumulator(self._persist, self._interval)
110+ self._monitor = CoverageMonitor(
111+ self.run_interval, 0.8, "Swift device usage snapshot",
112+ create_time=self._create_time)
113+ self.registry.reactor.call_every(
114+ self._monitor_interval, self._monitor.log)
115+
116 self.registry.reactor.call_on("stop", self._monitor.log, priority=2000)
117- self.call_on_accepted("swift-device-info", self.send_messages, True)
118-
119- def create_swift_device_info_message(self):
120- if self._swift_device_info:
121- message = {"type": "swift-device-info",
122- "swift-device-info": self._swift_device_info}
123- self._swift_device_info_to_persist = self._swift_device_info[:]
124- self._swift_device_info = []
125- return message
126- return None
127-
128- def send_messages(self, urgent=False):
129- message = self.create_swift_device_info_message()
130+ self.call_on_accepted("swift-usage", self.send_message, True)
131+
132+ def create_message(self):
133+ usage_points = self._swift_usage_points
134+ self._swift_usage_points = []
135+ if usage_points:
136+ return {"type": "swift-usage", "data-points": usage_points}
137+
138+ def send_message(self, urgent=False):
139+ message = self.create_message()
140 if message:
141- logging.info("Queueing message with updated swift device info.")
142- d = self.registry.broker.send_message(
143+ self.registry.broker.send_message(
144 message, self._session_id, urgent=urgent)
145- d.addCallback(lambda x: self.persist_swift_info())
146-
147- def exchange(self):
148- self.registry.broker.call_if_accepted("swift-device-info",
149- self.send_messages)
150-
151- def persist_swift_info(self):
152- for swift_device_info in self._swift_device_info_to_persist:
153- device_name = swift_device_info["device"]
154- key = (self.persist_name, device_name)
155- self._persist.set(key, swift_device_info)
156- self._swift_device_info_to_persist = None
157- # This forces the registry to write the persistent store to disk
158- # This means that the persistent data reflects the state of the
159- # messages sent.
160- self.registry.flush()
161+
162+ def exchange(self, urgent=False):
163+ self.registry.broker.call_if_accepted(
164+ "swift-usage", self.send_message, urgent)
165
166 def run(self):
167- if not self.enabled:
168+ if not self._should_run():
169 return
170+
171 self._monitor.ping()
172
173- current_swift_devices = self._get_swift_devices()
174- current_device_names = []
175- for swift_info in current_swift_devices:
176- device_name = swift_info["device"]
177- current_device_names.append(device_name)
178- key = (self.persist_name, device_name)
179- prev_swift_info = self._persist.get(key)
180- if not prev_swift_info or prev_swift_info != swift_info:
181- if swift_info not in self._swift_device_info:
182- self._swift_device_info.append(swift_info)
183-
184- # Get all persisted devices and remove those that no longer exist
185- persisted_devices = self._persist.get(self.persist_name)
186- if persisted_devices:
187- for device_name in persisted_devices.keys():
188- if device_name not in current_device_names:
189- self._persist.remove((self.persist_name, device_name))
190-
191- def _get_swift_devices(self):
192- config_file = self._swift_config
193- # Check if a swift storage config file is available. No need to run
194- # if we know that we're not on a swift monitor node anyway.
195- if not os.path.exists(config_file):
196- # There is no config file - it's not a swift storage machine.
197- self.enabled = False
198+ host = self._get_recon_host()
199+ deferred = threads.deferToThread(self._perform_recon_call, host)
200+ deferred.addCallback(self._handle_usage)
201+ return deferred
202+
203+ def _should_run(self):
204+ """Return whether the plugin should run."""
205+ if not self.active:
206+ return False
207+
208+ if not self._has_swift:
209 logging.info(
210- "This does not appear to be a swift storage server. '%s' "
211- "plugin has been disabled." % self.persist_name)
212- return []
213-
214- # Extract the swift service URL from the ringfile and cache it.
215- if self._swift_recon_url is None:
216- ring = self._get_ring()
217- if ring is None:
218- return []
219-
220- network_devices = self._get_network_devices()
221- local_ips = [device["ip_address"] for device in network_devices]
222-
223- # Grab first swift service with an IP on this host
224- for dev in ring.devs:
225- if dev and dev["ip"] in local_ips:
226- self._swift_recon_url = "http://%s:%d/recon/diskusage" % (
227- dev['ip'], dev['port'])
228- break
229-
230- if self._swift_recon_url is None:
231- self.enabled = False
232- logging.error(
233- "Local swift service not found. '%s' plugin has "
234- "been disabled." % self.persist_name)
235- return []
236-
237- recon_disk_info = self._get_swift_disk_usage()
238- # We don't care about avail and free figures because we track
239- # free_space for mounted devices in free-space messages
240- return [{"device": "/dev/%s" % device["device"],
241- "mounted": device["mounted"]} for device in recon_disk_info]
242-
243- def _get_swift_disk_usage(self):
244- """
245- Query the swift storage usage data by parsing the curled recon data
246- from http://localhost:<_swift_service_port>/recon/diskusage.
247- Lots of recon data for the picking described at:
248- http://docs.openstack.org/developer/swift/admin_guide.html
249- """
250- error_message = None
251- try:
252- content = self._fetch(self._swift_recon_url)
253- except HTTPCodeError, error:
254- error_message = (
255- "Swift service is running without swift-recon enabled.")
256- except (FetchError, PyCurlError), error:
257- error_message = (
258- "Swift service not available at %s. %s." %
259- (self._swift_recon_url, str(error)))
260- if error_message is not None:
261- self.enabled = False
262- logging.error("%s '%s' plugin has been disabled." % (
263- error_message, self.persist_name))
264- return None
265-
266- if not content:
267- return None
268-
269- swift_disk_usages = json.loads(content) # list of device dicts
270- return swift_disk_usages
271-
272- def _get_ring(self):
273- """Return ring-file object from self._swift_ring location"""
274+ "This machine does not appear to be a Swift machine. "
275+ "Deactivating plugin.")
276+ self.active = False
277+ return False
278+
279+ # Check for object ring config file.
280+ # If it is not present, it's not a Swift machine or it not yet set up.
281 if not os.path.exists(self._swift_ring):
282- logging.warning(
283- "Swift ring files are not available yet.")
284- return None
285- try:
286- from swift.common.ring import Ring
287- except ImportError:
288- self.enabled = False
289- logging.error(
290- "Swift python common libraries not found. '%s' plugin has "
291- "been disabled." % self.persist_name)
292- return None
293- return Ring(self._swift_ring)
294+ return False
295+
296+ return True
297+
298+ def _get_recon_host(self):
299+ """Return a tuple with Recon (host, port)."""
300+ local_ips = self._get_local_ips()
301+ ring = Ring(self._swift_ring)
302+ for dev in ring.devs:
303+ if dev and dev["ip"] in local_ips:
304+ return dev["ip"], dev["port"]
305+
306+ def _get_local_ips(self):
307+ """Return a list of IP addresses for local devices."""
308+ return [
309+ device["ip_address"] for device in get_active_device_info()]
310+
311+ def _perform_recon_call(self, host):
312+ """Get usage information from Swift Recon service."""
313+ if not host:
314+ return
315+
316+ scout = Scout("diskusage")
317+ # Perform the actual call
318+ _, disk_usage, code = scout.scout(host)
319+ if code == 200:
320+ return disk_usage
321+
322+ def _handle_usage(self, disk_usage):
323+ timestamp = int(self._create_time())
324+
325+ devices = set()
326+ for usage in disk_usage:
327+ if not usage["mounted"]:
328+ continue
329+
330+ device = usage["device"]
331+ devices.add(device)
332+
333+ step_values = []
334+ for key in ("size", "avail", "used"):
335+ # Store values in tree so it's easy to delete all values for a
336+ # device
337+ persist_key = "usage.%s.%s" % (device, key)
338+ step_value = self._accumulate(
339+ timestamp, usage[key], persist_key)
340+ step_values.append(step_value)
341+
342+ if all(step_values):
343+ point = [step_value[0], device] # accumulated timestamp
344+ point.extend(int(step_value[1]) for step_value in step_values)
345+ self._swift_usage_points.append(tuple(point))
346+
347+ # Update device list and remove usage for devices that no longer exist.
348+ current_devices = set(self._persist.get("devices", ()))
349+ for device in current_devices - devices:
350+ self._persist.remove("usage.%s" % device)
351+ self._persist.set("devices", list(devices))
352
353=== renamed file 'landscape/monitor/tests/test_swiftdeviceinfo.py' => 'landscape/monitor/tests/test_swiftusage.py'
354--- landscape/monitor/tests/test_swiftdeviceinfo.py 2013-07-12 13:41:07 +0000
355+++ landscape/monitor/tests/test_swiftusage.py 2014-05-21 06:46:43 +0000
356@@ -1,388 +1,263 @@
357 from twisted.internet.defer import succeed
358
359-from landscape.lib.fetch import HTTPCodeError
360-from landscape.monitor.swiftdeviceinfo import SwiftDeviceInfo
361+from landscape.monitor.swiftusage import SwiftUsage
362 from landscape.tests.helpers import LandscapeTest, MonitorHelper
363 from landscape.tests.mocker import ANY
364
365
366-class FakeRingInfo(object):
367+class FakeRing(object):
368 def __init__(self, ip_port_tuples=[]):
369- self.devs = []
370- for ip, port in ip_port_tuples:
371- self.devs.append({"ip": ip, "port": port})
372-
373-
374-class SwiftDeviceInfoTest(LandscapeTest):
375- """Tests for swift-device-info plugin."""
376+ self.devs = [
377+ {"ip": ip, "port": port}
378+ for ip, port in ip_port_tuples]
379+
380+
381+class SwiftUsageTest(LandscapeTest):
382+ """Tests for swift-usage plugin."""
383
384 helpers = [MonitorHelper]
385
386 def setUp(self):
387 LandscapeTest.setUp(self)
388- self.mstore.set_accepted_types(["swift-device-info"])
389+ self.mstore.set_accepted_types(["swift-usage"])
390+ self.plugin = SwiftUsage(
391+ create_time=self.reactor.time, swift_ring=self.makeFile("ring"))
392+ self.plugin._has_swift = True
393+
394+ def test_wb_should_run_not_active(self):
395+ """
396+ L{SwiftUsage._should_run} returns C{False} if plugin is not active.
397+ """
398+ plugin = SwiftUsage(create_time=self.reactor.time)
399+ plugin.active = False
400+ self.assertFalse(plugin._should_run())
401+
402+ def test_wb_should_run_no_swift(self):
403+ """
404+ L{SwiftUsage._should_run} returns C{False} if Swift client library is
405+ not available. It also disables the plugin.
406+ """
407+ plugin = SwiftUsage(create_time=self.reactor.time)
408+ plugin._has_swift = False
409+ self.assertFalse(plugin._should_run())
410+ self.assertFalse(plugin.active)
411+
412+ def test_wb_should_run_no_swift_ring(self):
413+ """
414+ L{SwiftUsage._should_run} returns C{False} if Swift ring configuration
415+ file is not found.
416+ """
417+ plugin = SwiftUsage(
418+ create_time=self.reactor.time, swift_ring=self.makeFile())
419+ plugin._has_swift = True
420+ self.assertFalse(plugin._should_run())
421+
422+ def test_wb_should_run(self):
423+ """
424+ L{SwiftUsage._should_run} returns C{True} if everything if the Swift
425+ ring is properly configured.
426+ """
427+ plugin = SwiftUsage(
428+ create_time=self.reactor.time, swift_ring=self.makeFile("ring"))
429+ plugin._has_swift = True
430+ self.assertTrue(plugin._should_run())
431
432 def test_exchange_messages(self):
433 """
434- The swift_device_info plugin queues message when manager.exchange()
435- is called. Each message should be aligned to a step boundary;
436- only a sing message with the latest swift device information will
437- be delivered in a single message.
438+ The plugin queues message when manager.exchange() is called.
439+ Each message should be aligned to a step boundary; only a sing message
440+ with the latest swift device information will be delivered in a single
441+ message.
442 """
443- def fake_swift_devices():
444- return [{"device": "/dev/hdf", "mounted": True},
445- {"device": "/dev/hda2", "mounted": False}]
446-
447- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
448- plugin._get_swift_devices = fake_swift_devices
449-
450- step_size = self.monitor.step_size
451- self.monitor.add(plugin)
452-
453- # Exchange should trigger a flush of the persist database
454- registry_mocker = self.mocker.replace(plugin.registry)
455- registry_mocker.flush()
456- self.mocker.result(None)
457- self.mocker.replay()
458-
459- self.reactor.advance(step_size * 2)
460+ points = [(1234, "sdb", 100000, 80000, 20000),
461+ (1234, "sdc", 200000, 120000, 800000)]
462+ self.plugin._swift_usage_points = points
463+
464+ self.monitor.add(self.plugin)
465 self.monitor.exchange()
466
467 messages = self.mstore.get_pending_messages()
468 self.assertEqual(len(messages), 1)
469- expected_message_content = [
470- {"device": "/dev/hdf", "mounted": True},
471- {"device": "/dev/hda2", "mounted": False}]
472-
473- swift_devices = messages[0]["swift-device-info"]
474- self.assertEqual(swift_devices, expected_message_content)
475-
476- def test_messaging_flushes(self):
477- """
478- Duplicate message should never be created. If no data is
479- available, None will be returned when messages are created.
480- """
481- def fake_swift_devices():
482- return [{"device": "/dev/hdf", "mounted": True},
483- {"device": "/dev/hda2", "mounted": False}]
484-
485- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
486- self.monitor.add(plugin)
487- plugin._get_swift_devices = fake_swift_devices
488+ data_points = messages[0]["data-points"]
489+ self.assertEqual(points, data_points)
490+
491+ def test_no_exchange_empty_messages(self):
492+ """
493+ If no usage data is available, no message is exchanged.
494+ """
495+ self.monitor.add(self.plugin)
496+ self.monitor.exchange()
497+
498+ self.assertEqual([], self.mstore.get_pending_messages())
499+
500+ def test_create_message(self):
501+ """L{SwiftUsage.create_message} returns a 'swift-usage' message."""
502+ points = [(1234, "sdb", 100000, 80000, 20000),
503+ (1234, "sdc", 200000, 120000, 80000)]
504+ self.plugin._swift_usage_points = points
505+ message = self.plugin.create_message()
506+ self.assertEqual(
507+ {"type": "swift-usage", "data-points": points}, message)
508+
509+ def test_create_message_empty(self):
510+ """
511+ L{SwiftUsage.create_message} returns C{None} if no data are available.
512+ """
513+ self.assertIs(None, self.plugin.create_message())
514+
515+ def test_crate_message_flushes(self):
516+ """Duplicate message should never be created."""
517+ self.monitor.add(self.plugin)
518+ self.plugin._swift_usage_points = [(1234, "sdb", 100000, 80000, 20000)]
519+ self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
520
521 self.reactor.advance(self.monitor.step_size)
522-
523- message = plugin.create_swift_device_info_message()
524- self.assertEqual(message.keys(), ["swift-device-info", "type"])
525-
526- message = plugin.create_swift_device_info_message()
527- self.assertEqual(message, None)
528-
529- def test_never_exchange_empty_messages(self):
530- """
531- When the plugin has no data, its various create_X_message()
532- methods will return None. Empty or null messages should never
533- be queued.
534- """
535- self.mstore.set_accepted_types(["load-average"])
536-
537- plugin = SwiftDeviceInfo()
538- self.monitor.add(plugin)
539- self.monitor.exchange()
540- self.assertEqual(len(self.mstore.get_pending_messages()), 0)
541-
542- def test_messages_with_swift_data(self):
543- """
544- All swift-affiliated devices are sent in swift-device-info messages.
545- Both mounted and unmounted swift devices send data.
546- """
547- def fake_swift_devices():
548- return [{"device": "/dev/hdf", "mounted": True},
549- {"device": "/dev/hda2", "mounted": False}]
550-
551- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
552-
553- plugin._get_swift_devices = fake_swift_devices
554-
555- step_size = self.monitor.step_size
556- self.monitor.add(plugin)
557- plugin.run()
558-
559- self.reactor.advance(step_size)
560- self.monitor.exchange()
561-
562- messages = self.mstore.get_pending_messages()
563- self.assertEqual(len(messages), 1)
564-
565- # Need to see both mounted and unmounted swift device info
566- self.assertEqual(
567- messages[0].get("swift-device-info"),
568- [{'device': u'/dev/hdf', 'mounted': True},
569- {'device': u'/dev/hda2', 'mounted': False}])
570-
571- def test_resynchronize(self):
572- """
573- On the reactor "resynchronize" event, new swift-device-info messages
574- should be sent.
575- """
576- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
577-
578- def fake_swift_devices():
579- return [{"device": "/dev/hdf", "mounted": True},
580- {"device": "/dev/hda2", "mounted": False}]
581-
582- self.monitor.add(plugin)
583- plugin._get_swift_devices = fake_swift_devices
584-
585- plugin.run()
586- plugin.exchange()
587- self.reactor.fire("resynchronize", scopes=["storage"])
588- plugin.run()
589- plugin.exchange()
590- messages = self.mstore.get_pending_messages()
591- expected_message = {
592- "type": "swift-device-info",
593- "swift-device-info": [
594- {"device": "/dev/hdf", "mounted": True},
595- {"device": "/dev/hda2", "mounted": False}]}
596- self.assertMessages(messages, [expected_message, expected_message])
597+ message = self.plugin.create_message()
598+ self.assertIsNot(None, message)
599+ message = self.plugin.create_message()
600+ self.assertIs(None, message)
601
602 def test_no_message_if_not_accepted(self):
603 """
604- Don't add any messages at all if the broker isn't currently
605- accepting their type.
606+ No message is sent if the broker isn't currently accepting their type.
607 """
608+ self.plugin._swift_usage_points = [(1234, "sdb", 100000, 80000, 20000)]
609+ self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
610+
611 self.mstore.set_accepted_types([])
612-
613- def fake_swift_devices():
614- return [{"device": "/dev/hdf", "mounted": True},
615- {"device": "/dev/hda2", "mounted": False}]
616-
617- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
618- self.monitor.add(plugin)
619- plugin._get_swift_devices = fake_swift_devices
620+ self.monitor.add(self.plugin)
621
622 self.reactor.advance(self.monitor.step_size * 2)
623 self.monitor.exchange()
624
625- self.mstore.set_accepted_types(["swift-device-info"])
626 self.assertMessages(list(self.mstore.get_pending_messages()), [])
627
628 def test_call_on_accepted(self):
629 """
630- When message type acceptance is added for swift-device-info,
631+ When message type acceptance is added for 'swift' message,
632 send_message gets called.
633 """
634- def fake_swift_devices():
635- return [{"device": "/dev/hdf", "mounted": True},
636- {"device": "/dev/hda2", "mounted": False}]
637-
638- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
639- self.monitor.add(plugin)
640- plugin._get_swift_devices = fake_swift_devices
641-
642- self.reactor.advance(plugin.run_interval)
643+ self.plugin._swift_usage_points = [(1234, "sdb", 100000, 80000, 20000)]
644+ self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
645+
646+ self.monitor.add(self.plugin)
647+ self.reactor.advance(self.plugin.run_interval)
648
649 remote_broker_mock = self.mocker.replace(self.remote)
650 remote_broker_mock.send_message(ANY, ANY, urgent=True)
651 self.mocker.result(succeed(None))
652- self.mocker.count(1) # 1 send message is called for swift-device-info
653+ self.mocker.count(1) # 1 send message is called
654 self.mocker.replay()
655
656 self.reactor.fire(
657- ("message-type-acceptance-changed", "swift-device-info"), True)
658-
659- def test_persist_deltas(self):
660- """
661- Swift persistent device info drops old devices from persist storage if
662- the device no longer exists in the current device list.
663- """
664- def fake_swift_devices():
665- return [{"device": "/dev/hdf", "mounted": True},
666- {"device": "/dev/hda2", "mounted": False}]
667-
668- def fake_swift_devices_no_hdf():
669- return [{"device": "/dev/hda2", "mounted": False}]
670-
671- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
672- self.monitor.add(plugin)
673- plugin._get_swift_devices = fake_swift_devices
674- plugin.run()
675- plugin.exchange() # To persist swift recon data
676- self.assertEqual(
677- plugin._persist.get("swift-device-info"),
678- {"/dev/hdf": {"device": "/dev/hdf", "mounted": True},
679- "/dev/hda2": {"device": "/dev/hda2", "mounted": False}})
680-
681- # Drop a device
682- plugin._get_swift_devices = fake_swift_devices_no_hdf
683- plugin.run()
684- plugin.exchange()
685- self.assertEqual(
686- plugin._persist.get("swift-device-info"),
687- {"/dev/hda2": {"device": "/dev/hda2", "mounted": False}})
688-
689- # Run again, calling create_swift_device_info_message which purges info
690- plugin.run()
691- plugin.exchange()
692- message3 = plugin.create_swift_device_info_message()
693- self.assertIdentical(message3, None)
694-
695- def test_persist_timing(self):
696- """Swift device info is only persisted when exchange happens.
697-
698- If an event happened between the persist and the exchange, the server
699- didn't get the mount info at all. This test ensures that mount info are
700- only saved when exchange happens.
701- """
702- def fake_swift_devices():
703- return [{"device": "/dev/hdf", "mounted": True},
704- {"device": "/dev/hda2", "mounted": False}]
705-
706- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
707- self.monitor.add(plugin)
708- plugin._get_swift_devices = fake_swift_devices
709- plugin.run()
710- message1 = plugin.create_swift_device_info_message()
711- self.assertEqual(
712- message1.get("swift-device-info"),
713- [{"device": "/dev/hdf", "mounted": True},
714- {"device": "/dev/hda2", "mounted": False}])
715- plugin.run()
716- message2 = plugin.create_swift_device_info_message()
717- self.assertEqual(
718- message2.get("swift-device-info"),
719- [{"device": "/dev/hdf", "mounted": True},
720- {"device": "/dev/hda2", "mounted": False}])
721- # Run again, calling create_swift_device_info_message which purges info
722- plugin.run()
723- plugin.exchange()
724- plugin.run()
725- message3 = plugin.create_swift_device_info_message()
726- self.assertIdentical(message3, None)
727-
728- def test_wb_get_swift_devices_when_not_a_swift_node(self):
729- """
730- When not a swift node, _get_swift_devices returns an empty list and
731- no error messages.
732- """
733- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
734- self.assertEqual(plugin._get_swift_devices(), [])
735-
736- def test_wb_get_swift_devices_when_on_a_swift_node(self):
737- """
738- When on a swift node, _get_swift_devices reports a warning if the ring
739- files don't exist yet.
740- """
741- plugin = SwiftDeviceInfo(create_time=self.reactor.time,
742- swift_config="/etc/hosts")
743- logging_mock = self.mocker.replace("logging.warning")
744- logging_mock("Swift ring files are not available yet.")
745- self.mocker.replay()
746- self.assertEqual(plugin._get_swift_devices(), [])
747-
748- def test_run_disabled_when_missing_swift_config(self):
749- """
750- When on a node that doesn't have the appropriate swift config file. The
751- plugin logs an info message and is disabled.
752- """
753- plugin = SwiftDeviceInfo(create_time=self.reactor.time,
754- swift_config="/config/file/doesnotexist")
755- logging_mock = self.mocker.replace("logging.info")
756- logging_mock("This does not appear to be a swift storage server. "
757- "'swift-device-info' plugin has been disabled.")
758- self.mocker.replay()
759- self.monitor.add(plugin)
760- self.assertEqual(plugin.enabled, True)
761- plugin.run()
762- self.assertEqual(plugin.enabled, False)
763-
764- def test_wb_get_swift_devices_no_swift_python_libs_available(self):
765- """
766- The plugin logs an error and doesn't find swift devices when it can't
767- import the swift python libs which it requires.
768- """
769- plugin = SwiftDeviceInfo(create_time=self.reactor.time,
770- swift_config="/etc/hosts",
771- swift_ring="/etc/hosts")
772-
773- logging_mock = self.mocker.replace("logging.error")
774- logging_mock("Swift python common libraries not found. "
775- "'swift-device-info' plugin has been disabled.")
776- self.mocker.replay()
777-
778- self.assertEqual(plugin._get_swift_devices(), [])
779-
780- def test_wb_get_swift_disk_usage_when_no_swift_service_running(self):
781- """
782- When the swift service is running, but recon middleware is not active,
783- the Swift storage usage logs an error.
784- """
785- self.log_helper.ignore_errors(".*")
786- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
787- plugin._swift_recon_url = "http://localhost:12654"
788- result = plugin._get_swift_disk_usage()
789- self.assertIs(None, result)
790- self.assertIn(
791- "Swift service not available at %s." % plugin._swift_recon_url,
792- self.logfile.getvalue())
793-
794- def test_wb_get_swift_disk_usage_when_no_recon_service_configured(self):
795- """
796- When the swift service is running, but recon middleware is not active,
797- an error is logged.
798- """
799- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
800-
801- plugin._swift_recon_url = "http://localhost:12654"
802-
803- def fetch_error(url):
804- raise HTTPCodeError(400, "invalid path: /recon/diskusage")
805- plugin._fetch = fetch_error
806-
807- logging_mock = self.mocker.replace("logging.error", passthrough=False)
808- logging_mock(
809- "Swift service is running without swift-recon enabled. "
810- "'swift-device-info' plugin has been disabled.")
811- self.mocker.result(None)
812- self.mocker.replay()
813-
814- result = plugin._get_swift_disk_usage()
815- self.assertIs(None, result)
816-
817- def test_wb_get_swift_usage_no_information(self):
818- """
819- When the swift recon service returns no disk usage information,
820- the _get_swift_disk_usage method returns None.
821- """
822- plugin = SwiftDeviceInfo(create_time=self.reactor.time)
823-
824- def fetch_none(url):
825- return None
826-
827- plugin._fetch = fetch_none
828-
829- result = plugin._get_swift_disk_usage()
830- self.assertEqual(None, result)
831-
832- def test_wb_get_swift_devices_no_matched_local_service(self):
833- """
834- The plugin logs an error when the swift ring file does not represent
835- a swift service running local IP address on the current node.
836- """
837- plugin = SwiftDeviceInfo(create_time=self.reactor.time,
838- swift_config="/etc/hosts")
839-
840- def get_fake_ring():
841- return FakeRingInfo([("192.168.1.10", 6000)])
842- plugin._get_ring = get_fake_ring
843-
844- def local_network_devices():
845- return [{"ip_address": "10.1.2.3"}]
846- plugin._get_network_devices = local_network_devices
847-
848- logging_mock = self.mocker.replace("logging.error")
849- logging_mock("Local swift service not found. "
850- "'swift-device-info' plugin has been disabled.")
851- self.mocker.replay()
852- self.assertEqual(plugin._get_swift_devices(), [])
853+ ("message-type-acceptance-changed", "swift-usage"), True)
854+
855+ def test_message_only_mounted_devices(self):
856+ """
857+ The plugin only collects usage for mounted devices.
858+ """
859+ recon_response = [
860+ {"device": "vdb",
861+ "mounted": True,
862+ "size": 100000,
863+ "avail": 80000,
864+ "used": 20000},
865+ {"device": "vdc",
866+ "mounted": False,
867+ "size": "",
868+ "avail": "",
869+ "used": ""},
870+ {"device": "vdd",
871+ "mounted": True,
872+ "size": 200000,
873+ "avail": 10000,
874+ "used": 190000}]
875+ self.plugin._perform_recon_call = lambda host: succeed(recon_response)
876+ self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
877+
878+ self.monitor.add(self.plugin)
879+ self.reactor.advance(self.plugin._interval)
880+ self.plugin._handle_usage(recon_response)
881+
882+ self.assertEqual(
883+ [(30, "vdb", 100000, 80000, 20000),
884+ (30, "vdd", 200000, 10000, 190000)],
885+ self.plugin._swift_usage_points)
886+ self.assertEqual(["vdb", "vdd"], self.plugin._persist.get("devices"))
887+ self.assertNotIn("vdc", self.plugin._persist.get("usage"))
888+
889+ def test_message_remove_disappeared_devices(self):
890+ """
891+ Usage for devices that have disappeared are removed from the persist.
892+ """
893+ recon_response = [
894+ {"device": "vdb",
895+ "mounted": True,
896+ "size": 100000,
897+ "avail": 80000,
898+ "used": 20000},
899+ {"device": "vdc",
900+ "mounted": True,
901+ "size": 200000,
902+ "avail": 10000,
903+ "used": 190000}]
904+ self.plugin._perform_recon_call = lambda host: succeed(recon_response)
905+ self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
906+
907+ self.monitor.add(self.plugin)
908+ self.reactor.advance(self.monitor.step_size)
909+ self.plugin._handle_usage(recon_response)
910+ self.assertEqual(
911+ ["vdb", "vdc"], sorted(self.plugin._persist.get("devices")))
912+
913+ recon_response = [
914+ {"device": "vdb",
915+ "mounted": True,
916+ "size": 100000,
917+ "avail": 70000,
918+ "used": 30000}]
919+ self.reactor.advance(self.monitor.step_size)
920+ self.plugin._handle_usage(recon_response)
921+ self.assertNotIn("vdc", self.plugin._persist.get("usage"))
922+ self.assertEqual(["vdb"], self.plugin._persist.get("devices"))
923+
924+ def test_message_remove_unmounted_devices(self):
925+ """
926+ Usage for devices that are no longer mounted are removed from the
927+ persist.
928+ """
929+ recon_response = [
930+ {"device": "vdb",
931+ "mounted": True,
932+ "size": 100000,
933+ "avail": 80000,
934+ "used": 20000},
935+ {"device": "vdc",
936+ "mounted": True,
937+ "size": 200000,
938+ "avail": 10000,
939+ "used": 190000}]
940+ self.plugin._perform_recon_call = lambda host: succeed(recon_response)
941+ self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
942+
943+ self.monitor.add(self.plugin)
944+ self.reactor.advance(self.monitor.step_size)
945+ self.plugin._handle_usage(recon_response)
946+ self.assertEqual(
947+ ["vdb", "vdc"], sorted(self.plugin._persist.get("devices")))
948+
949+ recon_response = [
950+ {"device": "vdb",
951+ "mounted": True,
952+ "size": 100000,
953+ "avail": 70000,
954+ "used": 30000},
955+ {"device": "vdc",
956+ "mounted": False,
957+ "size": "",
958+ "avail": "",
959+ "used": ""}]
960+ self.reactor.advance(self.monitor.step_size)
961+ self.plugin._handle_usage(recon_response)
962+ self.assertNotIn("vdc", self.plugin._persist.get("usage"))
963+ self.assertEqual(["vdb"], self.plugin._persist.get("devices"))

Subscribers

People subscribed via source and target branches

to all changes: