Merge lp:~tribaal/landscape-client/fix-1130130-backwards-incompatible-messgae into lp:~landscape/landscape-client/trunk

Proposed by Chris Glass
Status: Merged
Approved by: Chris Glass
Approved revision: 632
Merged at revision: 627
Proposed branch: lp:~tribaal/landscape-client/fix-1130130-backwards-incompatible-messgae
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 253 lines (+60/-40)
6 files modified
landscape/lib/network.py (+15/-7)
landscape/lib/tests/test_network.py (+14/-11)
landscape/message_schemas.py (+11/-3)
landscape/monitor/networkdevice.py (+16/-2)
landscape/monitor/tests/test_networkdevice.py (+2/-16)
landscape/schema.py (+2/-1)
To merge this branch: bzr merge lp:~tribaal/landscape-client/fix-1130130-backwards-incompatible-messgae
Reviewer Review Type Date Requested Status
Thomas Herve (community) Approve
Alberto Donato (community) Approve
Review via email: mp+149623@code.launchpad.net

Description of the change

This branch removes the previously introduced "speed" member of the "network-device" message, and adds another top-level key instead, called "device-speeds" that contains the interface, the speed, and a newly introduced "duplex" flag (to know if a link is in duplex mode or not).

Since we have logic in place to filter unknown top-level keys, landscape servers should not wedge computers even if they are older that the client (i.e. if they don't have the schema introduced bu this branch).

The new key is marked optional for the opposing case - a new server will not wedge computers with an older version of the client.

To post a comment you must log in.
Revision history for this message
Alberto Donato (ack) wrote :

Look good! +1

#1:
+ for interface in get_active_interfaces(sock):
+ if interface in skipped_interfaces:
+ continue
+ if skip_vlan and "." in interface:
+ continue
+ if skip_alias and ":" in interface:
+ continue

since the same logic is present in get_active_device_info, it'd be nice to extract it in a method, such as

+ for interface in get_active_interfaces(sock):
+ if skip_interface(interface, skipped_interfaces, skip_vlan, skip_alias):
+ continue

review: Approve
629. By Chris Glass

Refactored common skipping logic in a separate function, as suggested.

Revision history for this message
Thomas Herve (therve) wrote :

[1]
+ # avaialble. We'll just assume it's False in that case.

Typo: available.

[2] I think I would keep a single get_active_device_info function, and change the message in the plugin instead. This would mean only calling get_active_interfaces once, only creating one socket, etc.

Thanks!

review: Needs Fixing
630. By Chris Glass

Made suggested changes. There is only one method again in the network library,
and the message splitting is done in the plugin instead.

631. By Chris Glass

Typo run

Revision history for this message
Thomas Herve (therve) wrote :

Looks great, +1! Maybe put a comment in message_schemas to explain the weird thing we're doing here. You may want to wait a bit for landing this until the matching server branch is ready, too.

review: Approve
632. By Chris Glass

Added a little comment to the message schema

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'landscape/lib/network.py'
2--- landscape/lib/network.py 2013-02-15 17:20:21 +0000
3+++ landscape/lib/network.py 2013-02-21 13:39:30 +0000
4@@ -40,7 +40,7 @@
5 return struct.calcsize("l") == 8
6
7
8-# initialize the struct size as per the machine's archictecture
9+# initialize the struct size as per the machine's architecture
10 IF_STRUCT_SIZE = is_64() and IF_STRUCT_SIZE_64 or IF_STRUCT_SIZE_32
11
12
13@@ -51,7 +51,7 @@
14 """
15 max_interfaces = 128
16
17- # Setup an an array to hold our response, and initialized to null strings.
18+ # Setup an array to hold our response, and initialized to null strings.
19 interfaces = array.array("B", "\0" * max_interfaces * IF_STRUCT_SIZE)
20 buffer_size = interfaces.buffer_info()[0]
21 packed_bytes = struct.pack(
22@@ -157,8 +157,9 @@
23 sock, interface)
24 interface_info["netmask"] = get_netmask(sock, interface)
25 interface_info["flags"] = get_flags(sock, interface)
26- interface_info["speed"] = get_network_interface_speed(sock,
27- interface)
28+ speed, duplex = get_network_interface_speed(sock, interface)
29+ interface_info["speed"] = speed
30+ interface_info["duplex"] = duplex
31 results.append(interface_info)
32 finally:
33 del sock
34@@ -220,7 +221,7 @@
35 * 10, 100, 1000, 2500, 10000: The interface speed in Mbps
36 * -1: The interface does not support querying for max speed, such as
37 virtio devices for instance.
38- * 0: The cable is not connected to the interface. We cannot mesure
39+ * 0: The cable is not connected to the interface. We cannot measure
40 interface speed, but could if it was plugged in.
41 """
42 cmd_struct = struct.pack('I39s', ETHTOOL_GSET, '\x00' * 39)
43@@ -231,16 +232,23 @@
44 try:
45 fcntl.ioctl(sock, SIOCETHTOOL, packed) # Status ioctl() call
46 res = status_cmd.tostring()
47- speed = struct.unpack('12xH29x', res)[0]
48+ speed, duplex = struct.unpack('12xHB28x', res)
49 except IOError as e:
50 if e.errno != errno.EOPNOTSUPP:
51 raise e
52 # e is "Operation not supported".
53 speed = -1
54+ duplex = False
55
56 # Drivers apparently report speed as 65535 when the link is not available
57 # (cable unplugged for example).
58 if speed == 65535:
59 speed = 0
60
61- return speed
62+ # The drivers report "duplex" to be 255 when the information is not
63+ # available. We'll just assume it's False in that case.
64+ if duplex == 255:
65+ duplex = False
66+ duplex = bool(duplex)
67+
68+ return speed, duplex
69
70=== modified file 'landscape/lib/tests/test_network.py'
71--- landscape/lib/tests/test_network.py 2013-02-18 09:17:48 +0000
72+++ landscape/lib/tests/test_network.py 2013-02-21 13:39:30 +0000
73@@ -18,10 +18,10 @@
74 network devices, compare and verify the output against
75 that returned by ifconfig.
76 """
77- mock_get_interface_speed = self.mocker.replace(
78+ mock_get_network_interface_speed = self.mocker.replace(
79 get_network_interface_speed)
80- mock_get_interface_speed(ANY, ANY)
81- self.mocker.result(100)
82+ mock_get_network_interface_speed(ANY, ANY)
83+ self.mocker.result((100, True))
84 self.mocker.count(min=1, max=None)
85 self.mocker.replay()
86
87@@ -50,6 +50,7 @@
88 if flags & 4096:
89 self.assertIn("MULTICAST", block)
90 self.assertEqual(100, device["speed"])
91+ self.assertEqual(True, device["duplex"])
92
93 def test_skip_loopback(self):
94 """The C{lo} interface is not reported by L{get_active_device_info}."""
95@@ -225,12 +226,13 @@
96 self.mocker.result(0)
97
98 mock_unpack = self.mocker.replace("struct")
99- mock_unpack.unpack("12xH29x", ANY)
100- self.mocker.result((100,))
101+ mock_unpack.unpack("12xHB28x", ANY)
102+ self.mocker.result((100, False))
103
104 self.mocker.replay()
105
106- self.assertEqual(100, get_network_interface_speed(sock, "eth0"))
107+ self.assertEqual((100, False),
108+ get_network_interface_speed(sock, "eth0"))
109
110 def test_get_network_interface_speed_unplugged(self):
111 """
112@@ -241,15 +243,15 @@
113 # ioctl always succeeds
114 mock_ioctl = self.mocker.replace("fcntl")
115 mock_ioctl.ioctl(ANY, ANY, ANY)
116- self.mocker.result(0)
117+ self.mocker.result((0, False))
118
119 mock_unpack = self.mocker.replace("struct")
120- mock_unpack.unpack("12xH29x", ANY)
121- self.mocker.result((65535,))
122+ mock_unpack.unpack("12xHB28x", ANY)
123+ self.mocker.result((65535, False))
124
125 self.mocker.replay()
126
127- self.assertEqual(0, get_network_interface_speed(sock, "eth0"))
128+ self.assertEqual((0, False), get_network_interface_speed(sock, "eth0"))
129
130 def test_get_network_interface_speed_not_supported(self):
131 """
132@@ -270,7 +272,8 @@
133
134 self.mocker.replay()
135
136- self.assertEqual(-1, get_network_interface_speed(sock, "eth0"))
137+ self.assertEqual((-1, False),
138+ get_network_interface_speed(sock, "eth0"))
139
140 def test_get_network_interface_speed_other_io_error(self):
141 """
142
143=== modified file 'landscape/message_schemas.py'
144--- landscape/message_schemas.py 2013-02-18 14:28:44 +0000
145+++ landscape/message_schemas.py 2013-02-21 13:39:30 +0000
146@@ -403,6 +403,10 @@
147 "eucalyptus-info-error",
148 {"error": String()})
149
150+# The network-device message is split in two top level keys because we don't
151+# support adding sub-keys in a backwards-compatible way (only top-level keys).
152+# New servers will see an optional device-speeds key, and old servers will
153+# simply ignore the extra info..
154 NETWORK_DEVICE = Message(
155 "network-device",
156 {"devices": List(KeyDict({"interface": String(),
157@@ -410,9 +414,13 @@
158 "mac_address": String(),
159 "broadcast_address": String(),
160 "netmask": String(),
161- "flags": Int(),
162- "speed": Int()},
163- optional=["speed"]))})
164+ "flags": Int()})),
165+
166+ "device-speeds": List(KeyDict({"interface": String(),
167+ "speed": Int(),
168+ "duplex": Bool()}))},
169+ optional=["device-speeds"])
170+
171
172 NETWORK_ACTIVITY = Message(
173 "network-activity",
174
175=== modified file 'landscape/monitor/networkdevice.py'
176--- landscape/monitor/networkdevice.py 2010-12-10 13:57:11 +0000
177+++ landscape/monitor/networkdevice.py 2013-02-21 13:39:30 +0000
178@@ -20,5 +20,19 @@
179 super(NetworkDevice, self).register(registry)
180 self.call_on_accepted(self.message_type, self.exchange, True)
181
182- def get_data(self):
183- return self._device_info()
184+ def get_message(self):
185+ device_data = self._device_info()
186+ # Persist if the info is new.
187+ if self._persist.get("network-device-data") != device_data:
188+ self._persist.set("network-device-data", device_data)
189+ # We need to split the message in two top-level keys (see bug)
190+ device_speeds = []
191+ for device in device_data:
192+ speed_entry = {"interface": device["interface"]}
193+ speed_entry["speed"] = device.pop("speed")
194+ speed_entry["duplex"] = device.pop("duplex")
195+ device_speeds.append(speed_entry)
196+
197+ return {"type": self.message_type,
198+ "devices": device_data,
199+ "device-speeds": device_speeds}
200
201=== modified file 'landscape/monitor/tests/test_networkdevice.py'
202--- landscape/monitor/tests/test_networkdevice.py 2013-02-18 14:28:44 +0000
203+++ landscape/monitor/tests/test_networkdevice.py 2013-02-21 13:39:30 +0000
204@@ -1,5 +1,6 @@
205 from landscape.tests.helpers import LandscapeTest, MonitorHelper
206-from landscape.lib.network import get_active_device_info
207+from landscape.lib.network import (
208+ get_active_device_info)
209 from landscape.monitor.networkdevice import NetworkDevice
210
211
212@@ -35,21 +36,6 @@
213 self.assertEqual(8, flags & 8) # LOOPBACK
214 self.assertEqual(64, flags & 64) # RUNNING
215
216- def test_no_speed(self):
217- """
218- A message is sent with the device info, and no error is raised if the
219- speed parameter is omitted (to be backwards-compatible).
220- """
221- def test_get_active_device_info_no_speed():
222- devices = get_active_device_info(skipped_interfaces=())
223- for device in devices:
224- device.pop("speed")
225- return devices
226-
227- plugin = NetworkDevice(test_get_active_device_info_no_speed)
228- self.monitor.add(plugin)
229- plugin.exchange()
230-
231 def test_no_message_with_no_changes(self):
232 """If no device changes from the last message, no message is sent."""
233 self.plugin.exchange()
234
235=== modified file 'landscape/schema.py'
236--- landscape/schema.py 2011-07-28 12:54:34 +0000
237+++ landscape/schema.py 2013-02-21 13:39:30 +0000
238@@ -38,6 +38,7 @@
239 raise InvalidError("%r did not match any schema in %s"
240 % (value, self.schemas))
241
242+
243 class Bool(object):
244 """Something that must be a C{bool}."""
245 def coerce(self, value):
246@@ -128,7 +129,7 @@
247 @param schema: A sequence of schemas, which will be applied to
248 each value in the tuple respectively.
249 """
250-
251+
252 def __init__(self, *schema):
253 self.schema = schema
254

Subscribers

People subscribed via source and target branches

to all changes: