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

Proposed by Alberto Donato
Status: Merged
Approved by: Alberto Donato
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) Approve
Geoff Teale (community) Approve
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
778. By Alberto Donato

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

779. By Alberto Donato

Report usages as bytes, with integer type.

780. By Alberto Donato

Merge from trunk.

Revision history for this message
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
Revision history for this message
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()))})

Revision history for this message
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
781. By Alberto Donato

Change plugin interval.

782. By Alberto Donato

Address Geoff's review points.

783. By Alberto Donato

Rename swift message to swift-usage.

784. By Alberto Donato

Fix docstring.

Revision history for this message
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
=== modified file 'landscape/message_schemas.py'
--- landscape/message_schemas.py 2014-05-19 07:45:24 +0000
+++ landscape/message_schemas.py 2014-05-21 06:46:43 +0000
@@ -136,6 +136,10 @@
136 KeyDict({"device": Unicode(), "mounted": Bool()}))136 KeyDict({"device": Unicode(), "mounted": Bool()}))
137 })137 })
138138
139SWIFT_USAGE = Message("swift-usage", {
140 # Usage data points in the form (timestamp, device, size, avail, used)
141 "data-points": List(Tuple(Int(), Unicode(), Int(), Int(), Int()))})
142
139KEYSTONE_TOKEN = Message("keystone-token", {143KEYSTONE_TOKEN = Message("keystone-token", {
140 "data": Any(Bytes(), Constant(None))144 "data": Any(Bytes(), Constant(None))
141})145})
@@ -470,5 +474,5 @@
470 EUCALYPTUS_INFO_ERROR, NETWORK_DEVICE, NETWORK_ACTIVITY,474 EUCALYPTUS_INFO_ERROR, NETWORK_DEVICE, NETWORK_ACTIVITY,
471 REBOOT_REQUIRED_INFO, UPDATE_MANAGER_INFO, CPU_USAGE,475 REBOOT_REQUIRED_INFO, UPDATE_MANAGER_INFO, CPU_USAGE,
472 CEPH_USAGE, CEPH, SWIFT_DEVICE_INFO, KEYSTONE_TOKEN,476 CEPH_USAGE, CEPH, SWIFT_DEVICE_INFO, KEYSTONE_TOKEN,
473 CHANGE_HA_SERVICE, JUJU_INFO, CLOUD_METADATA]:477 CHANGE_HA_SERVICE, JUJU_INFO, CLOUD_METADATA, SWIFT_USAGE]:
474 message_schemas[schema.type] = schema478 message_schemas[schema.type] = schema
475479
=== modified file 'landscape/monitor/config.py'
--- landscape/monitor/config.py 2013-09-26 16:12:39 +0000
+++ landscape/monitor/config.py 2014-05-21 06:46:43 +0000
@@ -5,7 +5,7 @@
5 "LoadAverage", "MemoryInfo", "MountInfo", "ProcessorInfo",5 "LoadAverage", "MemoryInfo", "MountInfo", "ProcessorInfo",
6 "Temperature", "PackageMonitor", "UserMonitor",6 "Temperature", "PackageMonitor", "UserMonitor",
7 "RebootRequired", "AptPreferences", "NetworkActivity",7 "RebootRequired", "AptPreferences", "NetworkActivity",
8 "NetworkDevice", "UpdateManager", "CPUUsage", "SwiftDeviceInfo",8 "NetworkDevice", "UpdateManager", "CPUUsage", "SwiftUsage",
9 "CephUsage", "JujuInfo"]9 "CephUsage", "JujuInfo"]
1010
1111
1212
=== renamed file 'landscape/monitor/swiftdeviceinfo.py' => 'landscape/monitor/swiftusage.py'
--- landscape/monitor/swiftdeviceinfo.py 2013-07-08 14:31:52 +0000
+++ landscape/monitor/swiftusage.py 2014-05-21 06:46:43 +0000
@@ -1,183 +1,154 @@
1import logging1import logging
2import time2import time
3import os3import os
4import json4
55from twisted.internet import threads
6from landscape.lib.fetch import fetch, HTTPCodeError, PyCurlError, FetchError6
7from landscape.accumulate import Accumulator
7from landscape.lib.monitor import CoverageMonitor8from landscape.lib.monitor import CoverageMonitor
8from landscape.lib.network import get_active_device_info9from landscape.lib.network import get_active_device_info
9from landscape.monitor.plugin import MonitorPlugin10from landscape.monitor.plugin import MonitorPlugin
1011
1112try:
12class SwiftDeviceInfo(MonitorPlugin):13 from swift.common.ring import Ring
1314 from swift.cli.recon import Scout
14 persist_name = "swift-device-info"15 has_swift = True
16except ImportError:
17 has_swift = False
18
19
20class SwiftUsage(MonitorPlugin):
21 """Plugin reporting Swift cluster usage.
22
23 This only works if the client runs on a Swift node. It requires the
24 'python-swift' package to be installed (which is installed on swift nodes).
25
26 """
27
28 persist_name = "swift-usage"
15 scope = "storage"29 scope = "storage"
1630
17 def __init__(self, interval=300, monitor_interval=60 * 60,31 def __init__(self, interval=30, monitor_interval=60 * 60,
18 create_time=time.time,32 create_time=time.time,
19 swift_config="/etc/swift/object-server.conf",
20 swift_ring="/etc/swift/object.ring.gz"):33 swift_ring="/etc/swift/object.ring.gz"):
21 self.run_interval = interval34 self._interval = interval
22 self._monitor_interval = monitor_interval35 self._monitor_interval = monitor_interval
23 self._create_time = create_time36 self._create_time = create_time
24 self._fetch = fetch37 self._swift_ring = swift_ring # To discover Recon host/port
25 self._get_network_devices = get_active_device_info38
26 self._swift_config = swift_config # If exists, we are a swift node39 self._has_swift = has_swift
27 self._swift_ring = swift_ring # To discover swift recon port40 self._swift_usage_points = []
28 self._swift_recon_url = None41 self.active = True
29 self._create_time = create_time
30 self._swift_device_info = []
31 self._swift_device_info_to_persist = []
32 self.enabled = True
3342
34 def register(self, registry):43 def register(self, registry):
35 super(SwiftDeviceInfo, self).register(registry)44 super(SwiftUsage, self).register(registry)
36 self._monitor = CoverageMonitor(self.run_interval, 0.8,45 self._accumulate = Accumulator(self._persist, self._interval)
37 "swift device info snapshot",46 self._monitor = CoverageMonitor(
38 create_time=self._create_time)47 self.run_interval, 0.8, "Swift device usage snapshot",
39 self.registry.reactor.call_every(self._monitor_interval,48 create_time=self._create_time)
40 self._monitor.log)49 self.registry.reactor.call_every(
50 self._monitor_interval, self._monitor.log)
51
41 self.registry.reactor.call_on("stop", self._monitor.log, priority=2000)52 self.registry.reactor.call_on("stop", self._monitor.log, priority=2000)
42 self.call_on_accepted("swift-device-info", self.send_messages, True)53 self.call_on_accepted("swift-usage", self.send_message, True)
4354
44 def create_swift_device_info_message(self):55 def create_message(self):
45 if self._swift_device_info:56 usage_points = self._swift_usage_points
46 message = {"type": "swift-device-info",57 self._swift_usage_points = []
47 "swift-device-info": self._swift_device_info}58 if usage_points:
48 self._swift_device_info_to_persist = self._swift_device_info[:]59 return {"type": "swift-usage", "data-points": usage_points}
49 self._swift_device_info = []60
50 return message61 def send_message(self, urgent=False):
51 return None62 message = self.create_message()
52
53 def send_messages(self, urgent=False):
54 message = self.create_swift_device_info_message()
55 if message:63 if message:
56 logging.info("Queueing message with updated swift device info.")64 self.registry.broker.send_message(
57 d = self.registry.broker.send_message(
58 message, self._session_id, urgent=urgent)65 message, self._session_id, urgent=urgent)
59 d.addCallback(lambda x: self.persist_swift_info())66
6067 def exchange(self, urgent=False):
61 def exchange(self):68 self.registry.broker.call_if_accepted(
62 self.registry.broker.call_if_accepted("swift-device-info",69 "swift-usage", self.send_message, urgent)
63 self.send_messages)
64
65 def persist_swift_info(self):
66 for swift_device_info in self._swift_device_info_to_persist:
67 device_name = swift_device_info["device"]
68 key = (self.persist_name, device_name)
69 self._persist.set(key, swift_device_info)
70 self._swift_device_info_to_persist = None
71 # This forces the registry to write the persistent store to disk
72 # This means that the persistent data reflects the state of the
73 # messages sent.
74 self.registry.flush()
7570
76 def run(self):71 def run(self):
77 if not self.enabled:72 if not self._should_run():
78 return73 return
74
79 self._monitor.ping()75 self._monitor.ping()
8076
81 current_swift_devices = self._get_swift_devices()77 host = self._get_recon_host()
82 current_device_names = []78 deferred = threads.deferToThread(self._perform_recon_call, host)
83 for swift_info in current_swift_devices:79 deferred.addCallback(self._handle_usage)
84 device_name = swift_info["device"]80 return deferred
85 current_device_names.append(device_name)81
86 key = (self.persist_name, device_name)82 def _should_run(self):
87 prev_swift_info = self._persist.get(key)83 """Return whether the plugin should run."""
88 if not prev_swift_info or prev_swift_info != swift_info:84 if not self.active:
89 if swift_info not in self._swift_device_info:85 return False
90 self._swift_device_info.append(swift_info)86
9187 if not self._has_swift:
92 # Get all persisted devices and remove those that no longer exist
93 persisted_devices = self._persist.get(self.persist_name)
94 if persisted_devices:
95 for device_name in persisted_devices.keys():
96 if device_name not in current_device_names:
97 self._persist.remove((self.persist_name, device_name))
98
99 def _get_swift_devices(self):
100 config_file = self._swift_config
101 # Check if a swift storage config file is available. No need to run
102 # if we know that we're not on a swift monitor node anyway.
103 if not os.path.exists(config_file):
104 # There is no config file - it's not a swift storage machine.
105 self.enabled = False
106 logging.info(88 logging.info(
107 "This does not appear to be a swift storage server. '%s' "89 "This machine does not appear to be a Swift machine. "
108 "plugin has been disabled." % self.persist_name)90 "Deactivating plugin.")
109 return []91 self.active = False
11092 return False
111 # Extract the swift service URL from the ringfile and cache it.93
112 if self._swift_recon_url is None:94 # Check for object ring config file.
113 ring = self._get_ring()95 # If it is not present, it's not a Swift machine or it not yet set up.
114 if ring is None:
115 return []
116
117 network_devices = self._get_network_devices()
118 local_ips = [device["ip_address"] for device in network_devices]
119
120 # Grab first swift service with an IP on this host
121 for dev in ring.devs:
122 if dev and dev["ip"] in local_ips:
123 self._swift_recon_url = "http://%s:%d/recon/diskusage" % (
124 dev['ip'], dev['port'])
125 break
126
127 if self._swift_recon_url is None:
128 self.enabled = False
129 logging.error(
130 "Local swift service not found. '%s' plugin has "
131 "been disabled." % self.persist_name)
132 return []
133
134 recon_disk_info = self._get_swift_disk_usage()
135 # We don't care about avail and free figures because we track
136 # free_space for mounted devices in free-space messages
137 return [{"device": "/dev/%s" % device["device"],
138 "mounted": device["mounted"]} for device in recon_disk_info]
139
140 def _get_swift_disk_usage(self):
141 """
142 Query the swift storage usage data by parsing the curled recon data
143 from http://localhost:<_swift_service_port>/recon/diskusage.
144 Lots of recon data for the picking described at:
145 http://docs.openstack.org/developer/swift/admin_guide.html
146 """
147 error_message = None
148 try:
149 content = self._fetch(self._swift_recon_url)
150 except HTTPCodeError, error:
151 error_message = (
152 "Swift service is running without swift-recon enabled.")
153 except (FetchError, PyCurlError), error:
154 error_message = (
155 "Swift service not available at %s. %s." %
156 (self._swift_recon_url, str(error)))
157 if error_message is not None:
158 self.enabled = False
159 logging.error("%s '%s' plugin has been disabled." % (
160 error_message, self.persist_name))
161 return None
162
163 if not content:
164 return None
165
166 swift_disk_usages = json.loads(content) # list of device dicts
167 return swift_disk_usages
168
169 def _get_ring(self):
170 """Return ring-file object from self._swift_ring location"""
171 if not os.path.exists(self._swift_ring):96 if not os.path.exists(self._swift_ring):
172 logging.warning(97 return False
173 "Swift ring files are not available yet.")98
174 return None99 return True
175 try:100
176 from swift.common.ring import Ring101 def _get_recon_host(self):
177 except ImportError:102 """Return a tuple with Recon (host, port)."""
178 self.enabled = False103 local_ips = self._get_local_ips()
179 logging.error(104 ring = Ring(self._swift_ring)
180 "Swift python common libraries not found. '%s' plugin has "105 for dev in ring.devs:
181 "been disabled." % self.persist_name)106 if dev and dev["ip"] in local_ips:
182 return None107 return dev["ip"], dev["port"]
183 return Ring(self._swift_ring)108
109 def _get_local_ips(self):
110 """Return a list of IP addresses for local devices."""
111 return [
112 device["ip_address"] for device in get_active_device_info()]
113
114 def _perform_recon_call(self, host):
115 """Get usage information from Swift Recon service."""
116 if not host:
117 return
118
119 scout = Scout("diskusage")
120 # Perform the actual call
121 _, disk_usage, code = scout.scout(host)
122 if code == 200:
123 return disk_usage
124
125 def _handle_usage(self, disk_usage):
126 timestamp = int(self._create_time())
127
128 devices = set()
129 for usage in disk_usage:
130 if not usage["mounted"]:
131 continue
132
133 device = usage["device"]
134 devices.add(device)
135
136 step_values = []
137 for key in ("size", "avail", "used"):
138 # Store values in tree so it's easy to delete all values for a
139 # device
140 persist_key = "usage.%s.%s" % (device, key)
141 step_value = self._accumulate(
142 timestamp, usage[key], persist_key)
143 step_values.append(step_value)
144
145 if all(step_values):
146 point = [step_value[0], device] # accumulated timestamp
147 point.extend(int(step_value[1]) for step_value in step_values)
148 self._swift_usage_points.append(tuple(point))
149
150 # Update device list and remove usage for devices that no longer exist.
151 current_devices = set(self._persist.get("devices", ()))
152 for device in current_devices - devices:
153 self._persist.remove("usage.%s" % device)
154 self._persist.set("devices", list(devices))
184155
=== renamed file 'landscape/monitor/tests/test_swiftdeviceinfo.py' => 'landscape/monitor/tests/test_swiftusage.py'
--- landscape/monitor/tests/test_swiftdeviceinfo.py 2013-07-12 13:41:07 +0000
+++ landscape/monitor/tests/test_swiftusage.py 2014-05-21 06:46:43 +0000
@@ -1,388 +1,263 @@
1from twisted.internet.defer import succeed1from twisted.internet.defer import succeed
22
3from landscape.lib.fetch import HTTPCodeError3from landscape.monitor.swiftusage import SwiftUsage
4from landscape.monitor.swiftdeviceinfo import SwiftDeviceInfo
5from landscape.tests.helpers import LandscapeTest, MonitorHelper4from landscape.tests.helpers import LandscapeTest, MonitorHelper
6from landscape.tests.mocker import ANY5from landscape.tests.mocker import ANY
76
87
9class FakeRingInfo(object):8class FakeRing(object):
10 def __init__(self, ip_port_tuples=[]):9 def __init__(self, ip_port_tuples=[]):
11 self.devs = []10 self.devs = [
12 for ip, port in ip_port_tuples:11 {"ip": ip, "port": port}
13 self.devs.append({"ip": ip, "port": port})12 for ip, port in ip_port_tuples]
1413
1514
16class SwiftDeviceInfoTest(LandscapeTest):15class SwiftUsageTest(LandscapeTest):
17 """Tests for swift-device-info plugin."""16 """Tests for swift-usage plugin."""
1817
19 helpers = [MonitorHelper]18 helpers = [MonitorHelper]
2019
21 def setUp(self):20 def setUp(self):
22 LandscapeTest.setUp(self)21 LandscapeTest.setUp(self)
23 self.mstore.set_accepted_types(["swift-device-info"])22 self.mstore.set_accepted_types(["swift-usage"])
23 self.plugin = SwiftUsage(
24 create_time=self.reactor.time, swift_ring=self.makeFile("ring"))
25 self.plugin._has_swift = True
26
27 def test_wb_should_run_not_active(self):
28 """
29 L{SwiftUsage._should_run} returns C{False} if plugin is not active.
30 """
31 plugin = SwiftUsage(create_time=self.reactor.time)
32 plugin.active = False
33 self.assertFalse(plugin._should_run())
34
35 def test_wb_should_run_no_swift(self):
36 """
37 L{SwiftUsage._should_run} returns C{False} if Swift client library is
38 not available. It also disables the plugin.
39 """
40 plugin = SwiftUsage(create_time=self.reactor.time)
41 plugin._has_swift = False
42 self.assertFalse(plugin._should_run())
43 self.assertFalse(plugin.active)
44
45 def test_wb_should_run_no_swift_ring(self):
46 """
47 L{SwiftUsage._should_run} returns C{False} if Swift ring configuration
48 file is not found.
49 """
50 plugin = SwiftUsage(
51 create_time=self.reactor.time, swift_ring=self.makeFile())
52 plugin._has_swift = True
53 self.assertFalse(plugin._should_run())
54
55 def test_wb_should_run(self):
56 """
57 L{SwiftUsage._should_run} returns C{True} if everything if the Swift
58 ring is properly configured.
59 """
60 plugin = SwiftUsage(
61 create_time=self.reactor.time, swift_ring=self.makeFile("ring"))
62 plugin._has_swift = True
63 self.assertTrue(plugin._should_run())
2464
25 def test_exchange_messages(self):65 def test_exchange_messages(self):
26 """66 """
27 The swift_device_info plugin queues message when manager.exchange()67 The plugin queues message when manager.exchange() is called.
28 is called. Each message should be aligned to a step boundary;68 Each message should be aligned to a step boundary; only a sing message
29 only a sing message with the latest swift device information will69 with the latest swift device information will be delivered in a single
30 be delivered in a single message.70 message.
31 """71 """
32 def fake_swift_devices():72 points = [(1234, "sdb", 100000, 80000, 20000),
33 return [{"device": "/dev/hdf", "mounted": True},73 (1234, "sdc", 200000, 120000, 800000)]
34 {"device": "/dev/hda2", "mounted": False}]74 self.plugin._swift_usage_points = points
3575
36 plugin = SwiftDeviceInfo(create_time=self.reactor.time)76 self.monitor.add(self.plugin)
37 plugin._get_swift_devices = fake_swift_devices
38
39 step_size = self.monitor.step_size
40 self.monitor.add(plugin)
41
42 # Exchange should trigger a flush of the persist database
43 registry_mocker = self.mocker.replace(plugin.registry)
44 registry_mocker.flush()
45 self.mocker.result(None)
46 self.mocker.replay()
47
48 self.reactor.advance(step_size * 2)
49 self.monitor.exchange()77 self.monitor.exchange()
5078
51 messages = self.mstore.get_pending_messages()79 messages = self.mstore.get_pending_messages()
52 self.assertEqual(len(messages), 1)80 self.assertEqual(len(messages), 1)
53 expected_message_content = [81 data_points = messages[0]["data-points"]
54 {"device": "/dev/hdf", "mounted": True},82 self.assertEqual(points, data_points)
55 {"device": "/dev/hda2", "mounted": False}]83
5684 def test_no_exchange_empty_messages(self):
57 swift_devices = messages[0]["swift-device-info"]85 """
58 self.assertEqual(swift_devices, expected_message_content)86 If no usage data is available, no message is exchanged.
5987 """
60 def test_messaging_flushes(self):88 self.monitor.add(self.plugin)
61 """89 self.monitor.exchange()
62 Duplicate message should never be created. If no data is90
63 available, None will be returned when messages are created.91 self.assertEqual([], self.mstore.get_pending_messages())
64 """92
65 def fake_swift_devices():93 def test_create_message(self):
66 return [{"device": "/dev/hdf", "mounted": True},94 """L{SwiftUsage.create_message} returns a 'swift-usage' message."""
67 {"device": "/dev/hda2", "mounted": False}]95 points = [(1234, "sdb", 100000, 80000, 20000),
6896 (1234, "sdc", 200000, 120000, 80000)]
69 plugin = SwiftDeviceInfo(create_time=self.reactor.time)97 self.plugin._swift_usage_points = points
70 self.monitor.add(plugin)98 message = self.plugin.create_message()
71 plugin._get_swift_devices = fake_swift_devices99 self.assertEqual(
100 {"type": "swift-usage", "data-points": points}, message)
101
102 def test_create_message_empty(self):
103 """
104 L{SwiftUsage.create_message} returns C{None} if no data are available.
105 """
106 self.assertIs(None, self.plugin.create_message())
107
108 def test_crate_message_flushes(self):
109 """Duplicate message should never be created."""
110 self.monitor.add(self.plugin)
111 self.plugin._swift_usage_points = [(1234, "sdb", 100000, 80000, 20000)]
112 self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
72113
73 self.reactor.advance(self.monitor.step_size)114 self.reactor.advance(self.monitor.step_size)
74115 message = self.plugin.create_message()
75 message = plugin.create_swift_device_info_message()116 self.assertIsNot(None, message)
76 self.assertEqual(message.keys(), ["swift-device-info", "type"])117 message = self.plugin.create_message()
77118 self.assertIs(None, message)
78 message = plugin.create_swift_device_info_message()
79 self.assertEqual(message, None)
80
81 def test_never_exchange_empty_messages(self):
82 """
83 When the plugin has no data, its various create_X_message()
84 methods will return None. Empty or null messages should never
85 be queued.
86 """
87 self.mstore.set_accepted_types(["load-average"])
88
89 plugin = SwiftDeviceInfo()
90 self.monitor.add(plugin)
91 self.monitor.exchange()
92 self.assertEqual(len(self.mstore.get_pending_messages()), 0)
93
94 def test_messages_with_swift_data(self):
95 """
96 All swift-affiliated devices are sent in swift-device-info messages.
97 Both mounted and unmounted swift devices send data.
98 """
99 def fake_swift_devices():
100 return [{"device": "/dev/hdf", "mounted": True},
101 {"device": "/dev/hda2", "mounted": False}]
102
103 plugin = SwiftDeviceInfo(create_time=self.reactor.time)
104
105 plugin._get_swift_devices = fake_swift_devices
106
107 step_size = self.monitor.step_size
108 self.monitor.add(plugin)
109 plugin.run()
110
111 self.reactor.advance(step_size)
112 self.monitor.exchange()
113
114 messages = self.mstore.get_pending_messages()
115 self.assertEqual(len(messages), 1)
116
117 # Need to see both mounted and unmounted swift device info
118 self.assertEqual(
119 messages[0].get("swift-device-info"),
120 [{'device': u'/dev/hdf', 'mounted': True},
121 {'device': u'/dev/hda2', 'mounted': False}])
122
123 def test_resynchronize(self):
124 """
125 On the reactor "resynchronize" event, new swift-device-info messages
126 should be sent.
127 """
128 plugin = SwiftDeviceInfo(create_time=self.reactor.time)
129
130 def fake_swift_devices():
131 return [{"device": "/dev/hdf", "mounted": True},
132 {"device": "/dev/hda2", "mounted": False}]
133
134 self.monitor.add(plugin)
135 plugin._get_swift_devices = fake_swift_devices
136
137 plugin.run()
138 plugin.exchange()
139 self.reactor.fire("resynchronize", scopes=["storage"])
140 plugin.run()
141 plugin.exchange()
142 messages = self.mstore.get_pending_messages()
143 expected_message = {
144 "type": "swift-device-info",
145 "swift-device-info": [
146 {"device": "/dev/hdf", "mounted": True},
147 {"device": "/dev/hda2", "mounted": False}]}
148 self.assertMessages(messages, [expected_message, expected_message])
149119
150 def test_no_message_if_not_accepted(self):120 def test_no_message_if_not_accepted(self):
151 """121 """
152 Don't add any messages at all if the broker isn't currently122 No message is sent if the broker isn't currently accepting their type.
153 accepting their type.
154 """123 """
124 self.plugin._swift_usage_points = [(1234, "sdb", 100000, 80000, 20000)]
125 self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
126
155 self.mstore.set_accepted_types([])127 self.mstore.set_accepted_types([])
156128 self.monitor.add(self.plugin)
157 def fake_swift_devices():
158 return [{"device": "/dev/hdf", "mounted": True},
159 {"device": "/dev/hda2", "mounted": False}]
160
161 plugin = SwiftDeviceInfo(create_time=self.reactor.time)
162 self.monitor.add(plugin)
163 plugin._get_swift_devices = fake_swift_devices
164129
165 self.reactor.advance(self.monitor.step_size * 2)130 self.reactor.advance(self.monitor.step_size * 2)
166 self.monitor.exchange()131 self.monitor.exchange()
167132
168 self.mstore.set_accepted_types(["swift-device-info"])
169 self.assertMessages(list(self.mstore.get_pending_messages()), [])133 self.assertMessages(list(self.mstore.get_pending_messages()), [])
170134
171 def test_call_on_accepted(self):135 def test_call_on_accepted(self):
172 """136 """
173 When message type acceptance is added for swift-device-info,137 When message type acceptance is added for 'swift' message,
174 send_message gets called.138 send_message gets called.
175 """139 """
176 def fake_swift_devices():140 self.plugin._swift_usage_points = [(1234, "sdb", 100000, 80000, 20000)]
177 return [{"device": "/dev/hdf", "mounted": True},141 self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
178 {"device": "/dev/hda2", "mounted": False}]142
179143 self.monitor.add(self.plugin)
180 plugin = SwiftDeviceInfo(create_time=self.reactor.time)144 self.reactor.advance(self.plugin.run_interval)
181 self.monitor.add(plugin)
182 plugin._get_swift_devices = fake_swift_devices
183
184 self.reactor.advance(plugin.run_interval)
185145
186 remote_broker_mock = self.mocker.replace(self.remote)146 remote_broker_mock = self.mocker.replace(self.remote)
187 remote_broker_mock.send_message(ANY, ANY, urgent=True)147 remote_broker_mock.send_message(ANY, ANY, urgent=True)
188 self.mocker.result(succeed(None))148 self.mocker.result(succeed(None))
189 self.mocker.count(1) # 1 send message is called for swift-device-info149 self.mocker.count(1) # 1 send message is called
190 self.mocker.replay()150 self.mocker.replay()
191151
192 self.reactor.fire(152 self.reactor.fire(
193 ("message-type-acceptance-changed", "swift-device-info"), True)153 ("message-type-acceptance-changed", "swift-usage"), True)
194154
195 def test_persist_deltas(self):155 def test_message_only_mounted_devices(self):
196 """156 """
197 Swift persistent device info drops old devices from persist storage if157 The plugin only collects usage for mounted devices.
198 the device no longer exists in the current device list.158 """
199 """159 recon_response = [
200 def fake_swift_devices():160 {"device": "vdb",
201 return [{"device": "/dev/hdf", "mounted": True},161 "mounted": True,
202 {"device": "/dev/hda2", "mounted": False}]162 "size": 100000,
203163 "avail": 80000,
204 def fake_swift_devices_no_hdf():164 "used": 20000},
205 return [{"device": "/dev/hda2", "mounted": False}]165 {"device": "vdc",
206166 "mounted": False,
207 plugin = SwiftDeviceInfo(create_time=self.reactor.time)167 "size": "",
208 self.monitor.add(plugin)168 "avail": "",
209 plugin._get_swift_devices = fake_swift_devices169 "used": ""},
210 plugin.run()170 {"device": "vdd",
211 plugin.exchange() # To persist swift recon data171 "mounted": True,
212 self.assertEqual(172 "size": 200000,
213 plugin._persist.get("swift-device-info"),173 "avail": 10000,
214 {"/dev/hdf": {"device": "/dev/hdf", "mounted": True},174 "used": 190000}]
215 "/dev/hda2": {"device": "/dev/hda2", "mounted": False}})175 self.plugin._perform_recon_call = lambda host: succeed(recon_response)
216176 self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
217 # Drop a device177
218 plugin._get_swift_devices = fake_swift_devices_no_hdf178 self.monitor.add(self.plugin)
219 plugin.run()179 self.reactor.advance(self.plugin._interval)
220 plugin.exchange()180 self.plugin._handle_usage(recon_response)
221 self.assertEqual(181
222 plugin._persist.get("swift-device-info"),182 self.assertEqual(
223 {"/dev/hda2": {"device": "/dev/hda2", "mounted": False}})183 [(30, "vdb", 100000, 80000, 20000),
224184 (30, "vdd", 200000, 10000, 190000)],
225 # Run again, calling create_swift_device_info_message which purges info185 self.plugin._swift_usage_points)
226 plugin.run()186 self.assertEqual(["vdb", "vdd"], self.plugin._persist.get("devices"))
227 plugin.exchange()187 self.assertNotIn("vdc", self.plugin._persist.get("usage"))
228 message3 = plugin.create_swift_device_info_message()188
229 self.assertIdentical(message3, None)189 def test_message_remove_disappeared_devices(self):
230190 """
231 def test_persist_timing(self):191 Usage for devices that have disappeared are removed from the persist.
232 """Swift device info is only persisted when exchange happens.192 """
233193 recon_response = [
234 If an event happened between the persist and the exchange, the server194 {"device": "vdb",
235 didn't get the mount info at all. This test ensures that mount info are195 "mounted": True,
236 only saved when exchange happens.196 "size": 100000,
237 """197 "avail": 80000,
238 def fake_swift_devices():198 "used": 20000},
239 return [{"device": "/dev/hdf", "mounted": True},199 {"device": "vdc",
240 {"device": "/dev/hda2", "mounted": False}]200 "mounted": True,
241201 "size": 200000,
242 plugin = SwiftDeviceInfo(create_time=self.reactor.time)202 "avail": 10000,
243 self.monitor.add(plugin)203 "used": 190000}]
244 plugin._get_swift_devices = fake_swift_devices204 self.plugin._perform_recon_call = lambda host: succeed(recon_response)
245 plugin.run()205 self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
246 message1 = plugin.create_swift_device_info_message()206
247 self.assertEqual(207 self.monitor.add(self.plugin)
248 message1.get("swift-device-info"),208 self.reactor.advance(self.monitor.step_size)
249 [{"device": "/dev/hdf", "mounted": True},209 self.plugin._handle_usage(recon_response)
250 {"device": "/dev/hda2", "mounted": False}])210 self.assertEqual(
251 plugin.run()211 ["vdb", "vdc"], sorted(self.plugin._persist.get("devices")))
252 message2 = plugin.create_swift_device_info_message()212
253 self.assertEqual(213 recon_response = [
254 message2.get("swift-device-info"),214 {"device": "vdb",
255 [{"device": "/dev/hdf", "mounted": True},215 "mounted": True,
256 {"device": "/dev/hda2", "mounted": False}])216 "size": 100000,
257 # Run again, calling create_swift_device_info_message which purges info217 "avail": 70000,
258 plugin.run()218 "used": 30000}]
259 plugin.exchange()219 self.reactor.advance(self.monitor.step_size)
260 plugin.run()220 self.plugin._handle_usage(recon_response)
261 message3 = plugin.create_swift_device_info_message()221 self.assertNotIn("vdc", self.plugin._persist.get("usage"))
262 self.assertIdentical(message3, None)222 self.assertEqual(["vdb"], self.plugin._persist.get("devices"))
263223
264 def test_wb_get_swift_devices_when_not_a_swift_node(self):224 def test_message_remove_unmounted_devices(self):
265 """225 """
266 When not a swift node, _get_swift_devices returns an empty list and226 Usage for devices that are no longer mounted are removed from the
267 no error messages.227 persist.
268 """228 """
269 plugin = SwiftDeviceInfo(create_time=self.reactor.time)229 recon_response = [
270 self.assertEqual(plugin._get_swift_devices(), [])230 {"device": "vdb",
271231 "mounted": True,
272 def test_wb_get_swift_devices_when_on_a_swift_node(self):232 "size": 100000,
273 """233 "avail": 80000,
274 When on a swift node, _get_swift_devices reports a warning if the ring234 "used": 20000},
275 files don't exist yet.235 {"device": "vdc",
276 """236 "mounted": True,
277 plugin = SwiftDeviceInfo(create_time=self.reactor.time,237 "size": 200000,
278 swift_config="/etc/hosts")238 "avail": 10000,
279 logging_mock = self.mocker.replace("logging.warning")239 "used": 190000}]
280 logging_mock("Swift ring files are not available yet.")240 self.plugin._perform_recon_call = lambda host: succeed(recon_response)
281 self.mocker.replay()241 self.plugin._get_recon_host = lambda: ("192.168.1.10", 6000)
282 self.assertEqual(plugin._get_swift_devices(), [])242
283243 self.monitor.add(self.plugin)
284 def test_run_disabled_when_missing_swift_config(self):244 self.reactor.advance(self.monitor.step_size)
285 """245 self.plugin._handle_usage(recon_response)
286 When on a node that doesn't have the appropriate swift config file. The246 self.assertEqual(
287 plugin logs an info message and is disabled.247 ["vdb", "vdc"], sorted(self.plugin._persist.get("devices")))
288 """248
289 plugin = SwiftDeviceInfo(create_time=self.reactor.time,249 recon_response = [
290 swift_config="/config/file/doesnotexist")250 {"device": "vdb",
291 logging_mock = self.mocker.replace("logging.info")251 "mounted": True,
292 logging_mock("This does not appear to be a swift storage server. "252 "size": 100000,
293 "'swift-device-info' plugin has been disabled.")253 "avail": 70000,
294 self.mocker.replay()254 "used": 30000},
295 self.monitor.add(plugin)255 {"device": "vdc",
296 self.assertEqual(plugin.enabled, True)256 "mounted": False,
297 plugin.run()257 "size": "",
298 self.assertEqual(plugin.enabled, False)258 "avail": "",
299259 "used": ""}]
300 def test_wb_get_swift_devices_no_swift_python_libs_available(self):260 self.reactor.advance(self.monitor.step_size)
301 """261 self.plugin._handle_usage(recon_response)
302 The plugin logs an error and doesn't find swift devices when it can't262 self.assertNotIn("vdc", self.plugin._persist.get("usage"))
303 import the swift python libs which it requires.263 self.assertEqual(["vdb"], self.plugin._persist.get("devices"))
304 """
305 plugin = SwiftDeviceInfo(create_time=self.reactor.time,
306 swift_config="/etc/hosts",
307 swift_ring="/etc/hosts")
308
309 logging_mock = self.mocker.replace("logging.error")
310 logging_mock("Swift python common libraries not found. "
311 "'swift-device-info' plugin has been disabled.")
312 self.mocker.replay()
313
314 self.assertEqual(plugin._get_swift_devices(), [])
315
316 def test_wb_get_swift_disk_usage_when_no_swift_service_running(self):
317 """
318 When the swift service is running, but recon middleware is not active,
319 the Swift storage usage logs an error.
320 """
321 self.log_helper.ignore_errors(".*")
322 plugin = SwiftDeviceInfo(create_time=self.reactor.time)
323 plugin._swift_recon_url = "http://localhost:12654"
324 result = plugin._get_swift_disk_usage()
325 self.assertIs(None, result)
326 self.assertIn(
327 "Swift service not available at %s." % plugin._swift_recon_url,
328 self.logfile.getvalue())
329
330 def test_wb_get_swift_disk_usage_when_no_recon_service_configured(self):
331 """
332 When the swift service is running, but recon middleware is not active,
333 an error is logged.
334 """
335 plugin = SwiftDeviceInfo(create_time=self.reactor.time)
336
337 plugin._swift_recon_url = "http://localhost:12654"
338
339 def fetch_error(url):
340 raise HTTPCodeError(400, "invalid path: /recon/diskusage")
341 plugin._fetch = fetch_error
342
343 logging_mock = self.mocker.replace("logging.error", passthrough=False)
344 logging_mock(
345 "Swift service is running without swift-recon enabled. "
346 "'swift-device-info' plugin has been disabled.")
347 self.mocker.result(None)
348 self.mocker.replay()
349
350 result = plugin._get_swift_disk_usage()
351 self.assertIs(None, result)
352
353 def test_wb_get_swift_usage_no_information(self):
354 """
355 When the swift recon service returns no disk usage information,
356 the _get_swift_disk_usage method returns None.
357 """
358 plugin = SwiftDeviceInfo(create_time=self.reactor.time)
359
360 def fetch_none(url):
361 return None
362
363 plugin._fetch = fetch_none
364
365 result = plugin._get_swift_disk_usage()
366 self.assertEqual(None, result)
367
368 def test_wb_get_swift_devices_no_matched_local_service(self):
369 """
370 The plugin logs an error when the swift ring file does not represent
371 a swift service running local IP address on the current node.
372 """
373 plugin = SwiftDeviceInfo(create_time=self.reactor.time,
374 swift_config="/etc/hosts")
375
376 def get_fake_ring():
377 return FakeRingInfo([("192.168.1.10", 6000)])
378 plugin._get_ring = get_fake_ring
379
380 def local_network_devices():
381 return [{"ip_address": "10.1.2.3"}]
382 plugin._get_network_devices = local_network_devices
383
384 logging_mock = self.mocker.replace("logging.error")
385 logging_mock("Local swift service not found. "
386 "'swift-device-info' plugin has been disabled.")
387 self.mocker.replay()
388 self.assertEqual(plugin._get_swift_devices(), [])

Subscribers

People subscribed via source and target branches

to all changes: