Merge lp:~fcorrea/landscape-client/add-vminfo-to-computer-info-message into lp:~landscape/landscape-client/trunk

Proposed by Fernando Correa Neto
Status: Merged
Approved by: Free Ekanayaka
Approved revision: 333
Merge reported by: Thomas Herve
Merged at revision: not available
Proposed branch: lp:~fcorrea/landscape-client/add-vminfo-to-computer-info-message
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 291 lines (+211/-5)
3 files modified
landscape/message_schemas.py (+3/-2)
landscape/monitor/computerinfo.py (+40/-1)
landscape/monitor/tests/test_computerinfo.py (+168/-2)
To merge this branch: bzr merge lp:~fcorrea/landscape-client/add-vminfo-to-computer-info-message
Reviewer Review Type Date Requested Status
Free Ekanayaka (community) Approve
Thomas Herve (community) Approve
Landscape Pending
Review via email: mp+58332@code.launchpad.net

Description of the change

This branch adds and extra optional field, vm-info, to computer info messages, so that the server knows if the computer running the client, is virtualized or not.

To post a comment you must log in.
Revision history for this message
Thomas Herve (therve) wrote :

[1] You don't have any specific tests for _get_vm_info? I thought that was the point of passing root_path.

[2]
+ xen_info = [u"proc/sys/xen", u"sys/bus/xen", u"proc/xen"]

There is no need to have unicode paths.

[3]
+ elif True in map(exists, xen_info):

It's slightly more explicit to me to do "elif filter(exists, xen_info):"

[4]
+ elif "QEMU Virtual CPU" in open(
+ join_root_path(u"proc/cpuinfo")).read():
+ virt_info = "KVM"

You should close the open file explicitly.

[5]
+import os.path

You can import os directly.

Thanks!

review: Needs Fixing
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

What Thomas said (especially about testing) plus some other comments.

Just to elaborate on test coverage thing, a rule of thumb is that any change in the code (like removing a line, an if branch, etc.) should result in a test failure. In this case replacing the entire body of ComputerInfo._get_vm_info with a simple 'return ""' doesn't make any tests fail, which means that its behavior is untested.

[1]

+ def _get_vm_info(self, root_path="/"):

Please pass root_path to the constructor instead, like:

    def __init__(self, get_fqdn=get_fqdn,
                 meminfo_file="/proc/meminfo",
                 lsb_release_filename=LSB_RELEASE_FILENAME,
                 root_path="/"):
        self._get_fqdn = get_fqdn
        self._meminfo_file = meminfo_file
        self._lsb_release_filename = lsb_release_filename
        self._root_path = root_path

and then you can have something like

    root_path = self.makeDir()
    plugin = ComputerInfo(root_path=root_path)

in your vm-info-related tests, where the test populates root_path by hand for checking the behavior of the method.

To avoid cluttering the constructor with arguments that are only meaningful for tests, I think it would be better to use class variables that one can override in the tests:

class ComputerInfo(MonitorPlugin):

    get_fqdn = get_fqdn
    meminfo_file = "/proc/meminfo"
    lsb_release_filename = LSB_RELEASE_FILENAME
    root_path = "/"

and then

    plugin = ComputerInfo()
    plugin.root_path = self.makeDir()

We use this pattern in other places, but consider that this piece of code already uses test-oriented constructor parameters, it's fine to be consistent.

[2]

+ virt_info = "OpenVZ"

Please use lowercase strings, like "openvz" "xen" and "kvm", that's one chance less to get spelling errors while writing server side code/tests that use them. If this information must be eventually shown in some UI, it will be the UI's job to map those lowercase strings to nice human-oriented strings/phrases.

[3]

- def test_resynchronize(self):
+ def test_wb_resynchronize(self):

and

+ plugin._get_vm_info = lambda: ""

Once [1] is addressed I believe you can revert these changes.

[4]

+ xen_info = [u"proc/sys/xen", u"sys/bus/xen", u"proc/xen"]
+ xen_info = map(join_root_path, xen_info)

Maybe this variable would be better named "xen_paths".

review: Needs Fixing
331. By Fernando Correa Neto

- Addressing therve and free view comments

332. By Fernando Correa Neto

- Backporting tests from old revision

333. By Fernando Correa Neto

- Linting

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

Looks good!

[6]
+ elif exists(join_root_path("proc/cpuinfo")):
+ with open(join_root_path("proc/cpuinfo")) as cpuinfo:
+ if "QEMU Virtual CPU" in cpuinfo.read():
+ virt_info = "kvm"

You don't need to check for the existence of cpuinfo (it should always be there). You can't use "with" because we're still supporting python 2.5 in the client. You could use a future import, but I'd rather see you use try/finally here.

+1!

review: Approve
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Looks good, just a few more minor things. +1 with those fixed!

[5]

+ path_join = os.path.join

and

+ def join_root_path(path):
+ return path_join(self._root_path, path)

I'd avoid setting the the path_join variable, just call os.path.join directly, like:

         def join_root_path(path):
             return os.path.join(self._root_path, path)

it's one less line of code and little tiny bit easier to parse (you don't have to know that path_join is os.path.join).

The same holds for

+ exists = os.path.exists

and

+ elif filter(exists, xen_paths):
+ virt_info = "xen"

[6]

There should be a test checking that if "proc/cpuinfo" exists and does *not* contain the "QEMU Virtual CPU" string, then the vm-info will be empty. Without such a test one could simply remove the "if" and "open" check and all tests would pass, like:

        elif exists(join_root_path("proc/cpuinfo")):
            virt_info = "kvm"

[7]

- "total-swap": 1584}
+ "total-swap": 1584, "vm-info": ""}

I think in this test (test_resynchronize) we should also pass a root_path=self.makeDir() to the plugin constructor, otherwise the test would fail when run on KVM machine (which is not unlikely if we want to run the suite in an Eucalyptus cloud instance)

[8]

There's a test missing for the openvz case.

review: Approve
334. By Fernando Correa Neto

- Addressing final therve and free review comments

335. By Fernando Correa Neto

- Adding missing test for openvz and also adding root_path to the constructor of ComputerInfo in test_resynchronize test

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 2010-06-28 12:52:39 +0000
3+++ landscape/message_schemas.py 2011-04-20 21:43:28 +0000
4@@ -68,10 +68,11 @@
5 "computer-info",
6 {"hostname": utf8,
7 "total-memory": Int(),
8- "total-swap": Int()},
9+ "total-swap": Int(),
10+ "vm-info": String()},
11 # Not sure why these are all optional, but it's explicitly tested
12 # in the server
13- optional=["hostname", "total-memory", "total-swap"])
14+ optional=["hostname", "total-memory", "total-swap", "vm-info"])
15
16 DISTRIBUTION_INFO = Message(
17 "distribution-info",
18
19=== modified file 'landscape/monitor/computerinfo.py'
20--- landscape/monitor/computerinfo.py 2010-12-10 13:57:11 +0000
21+++ landscape/monitor/computerinfo.py 2011-04-20 21:43:28 +0000
22@@ -1,4 +1,5 @@
23 import logging
24+import os
25
26 from landscape.lib.lsb_release import LSB_RELEASE_FILENAME, parse_lsb_release
27 from landscape.lib.network import get_fqdn
28@@ -16,10 +17,12 @@
29
30 def __init__(self, get_fqdn=get_fqdn,
31 meminfo_file="/proc/meminfo",
32- lsb_release_filename=LSB_RELEASE_FILENAME):
33+ lsb_release_filename=LSB_RELEASE_FILENAME,
34+ root_path="/"):
35 self._get_fqdn = get_fqdn
36 self._meminfo_file = meminfo_file
37 self._lsb_release_filename = lsb_release_filename
38+ self._root_path = root_path
39
40 def register(self, registry):
41 super(ComputerInfo, self).register(registry)
42@@ -57,6 +60,8 @@
43 self._add_if_new(message, "total-memory",
44 total_memory)
45 self._add_if_new(message, "total-swap", total_swap)
46+ vm_info = self._get_vm_info()
47+ self._add_if_new(message, "vm-info", vm_info)
48 return message
49
50 def _add_if_new(self, message, key, value):
51@@ -90,3 +95,37 @@
52 message = {}
53 message.update(parse_lsb_release(self._lsb_release_filename))
54 return message
55+
56+ def _get_vm_info(self):
57+ """
58+ This is a utility that returns the virtualization type
59+
60+ It loops through some possible configurations and return a string with
61+ the name of the technology being used or None if there's no match
62+ """
63+ virt_info = ""
64+
65+ def join_root_path(path):
66+ return os.path.join(self._root_path, path)
67+
68+ xen_paths = ["proc/sys/xen", "sys/bus/xen", "proc/xen"]
69+ xen_paths = map(join_root_path, xen_paths)
70+
71+ vz_path = os.path.join(self._root_path, "proc/vz")
72+ if os.path.exists(vz_path):
73+ virt_info = "openvz"
74+
75+ elif filter(os.path.exists, xen_paths):
76+ virt_info = "xen"
77+
78+ cpu_info_path = os.path.join(self._root_path, "proc/cpuinfo")
79+ if os.path.exists(cpu_info_path):
80+ try:
81+ fd = open(cpu_info_path)
82+ cpuinfo = fd.read()
83+ if "QEMU Virtual CPU" in cpuinfo:
84+ virt_info = "kvm"
85+ finally:
86+ fd.close()
87+
88+ return virt_info
89
90=== modified file 'landscape/monitor/tests/test_computerinfo.py'
91--- landscape/monitor/tests/test_computerinfo.py 2010-04-28 13:07:53 +0000
92+++ landscape/monitor/tests/test_computerinfo.py 2011-04-20 21:43:28 +0000
93@@ -1,4 +1,5 @@
94 import re
95+import os
96
97 from landscape.monitor.computerinfo import ComputerInfo
98 from landscape.tests.helpers import LandscapeTest, MonitorHelper
99@@ -44,6 +45,27 @@
100 VmallocChunk: 107432 kB
101 """
102
103+ sample_kvm_cpuinfo = """
104+processor : 0
105+vendor_id : GenuineIntel
106+cpu family : 6
107+model : 2
108+model name : QEMU Virtual CPU version 0.14.0
109+stepping : 3
110+cpu MHz : 2653.112
111+cache size : 4096 KB
112+fpu : yes
113+fpu_exception : yes
114+cpuid level : 4
115+wp : yes
116+flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca
117+bogomips : 5306.22
118+clflush size : 64
119+cache_alignment : 64
120+address sizes : 40 bits physical, 48 bits virtual
121+power management:
122+"""
123+
124 def setUp(self):
125 LandscapeTest.setUp(self)
126 self.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
127@@ -272,14 +294,15 @@
128 meminfo_filename = self.makeFile(self.sample_memory_info)
129 plugin = ComputerInfo(get_fqdn=get_fqdn,
130 meminfo_file=meminfo_filename,
131- lsb_release_filename=self.lsb_release_filename)
132+ lsb_release_filename=self.lsb_release_filename,
133+ root_path=self.makeDir())
134 self.monitor.add(plugin)
135 plugin.exchange()
136 self.reactor.fire("resynchronize")
137 plugin.exchange()
138 computer_info = {"type": "computer-info", "hostname": "ooga.local",
139 "timestamp": 0, "total-memory": 1510,
140- "total-swap": 1584}
141+ "total-swap": 1584, "vm-info": ""}
142 dist_info = {"type": "distribution-info",
143 "code-name": "dapper", "description": "Ubuntu 6.06.1 LTS",
144 "distributor-id": "Ubuntu", "release": "6.06"}
145@@ -324,3 +347,146 @@
146
147 self.mstore.set_accepted_types(["distribution-info", "computer-info"])
148 self.assertMessages(list(self.mstore.get_pending_messages()), [])
149+
150+ def test_vminfo_empty_when_no_virtualization_is_found(self):
151+ """
152+ L{ComputerInfo} should always have the vm-info key even when no
153+ virtualization is used.
154+
155+ And it should be an empty string.
156+ """
157+ self.mstore.set_accepted_types(["computer-info"])
158+ root_path = self.makeDir()
159+ plugin = ComputerInfo(root_path=root_path)
160+ self.monitor.add(plugin)
161+
162+ plugin.exchange()
163+ message = self.mstore.get_pending_messages()[0]
164+ self.assertTrue("vm-info" in message)
165+ self.assertEqual("", message["vm-info"])
166+
167+ def test_vminfo_is_openvz_when_proc_vz_exists(self):
168+ """
169+ L{ComputerInfo} should have 'openvz' as the value of vm-info when
170+ /proc/vz exists.
171+ """
172+ root_path = self.makeDir()
173+ proc_path = os.path.join(root_path, "proc")
174+ self.makeDir(path=proc_path)
175+
176+ proc_vz_path = os.path.join(proc_path, "vz")
177+ self.makeFile(path=proc_vz_path, content="foo")
178+
179+ self.mstore.set_accepted_types(["computer-info"])
180+ plugin = ComputerInfo(root_path=root_path)
181+ self.monitor.add(plugin)
182+
183+ plugin.exchange()
184+ message = self.mstore.get_pending_messages()[0]
185+ self.assertEquals('openvz', message["vm-info"])
186+
187+ def test_vminfo_is_xen_when_proc_sys_xen_exists(self):
188+ """
189+ L{ComputerInfo} should have 'xen' as the value of vm-info when
190+ /proc/sys/xen exists.
191+ """
192+ root_path = self.makeDir()
193+ proc_path = os.path.join(root_path, "proc")
194+ self.makeDir(path=proc_path)
195+
196+ proc_sys_path = os.path.join(proc_path, "sys")
197+ self.makeDir(path=proc_sys_path)
198+
199+ proc_sys_xen_path = os.path.join(proc_sys_path, "xen")
200+ self.makeFile(path=proc_sys_xen_path, content="foo")
201+
202+ self.mstore.set_accepted_types(["computer-info"])
203+ plugin = ComputerInfo(root_path=root_path)
204+ self.monitor.add(plugin)
205+
206+ plugin.exchange()
207+ message = self.mstore.get_pending_messages()[0]
208+ self.assertEquals('xen', message["vm-info"])
209+
210+ def test_vminfo_is_xen_when_sys_bus_xen_exists(self):
211+ """
212+ L{ComputerInfo} should have 'xen' as value of vm-info when
213+ /sys/bus/xen exists
214+ """
215+ root_path = self.makeDir()
216+ sys_path = os.path.join(root_path, "sys")
217+ self.makeDir(path=sys_path)
218+
219+ sys_bus_path = os.path.join(sys_path, "bus")
220+ self.makeDir(path=sys_bus_path)
221+
222+ sys_bus_xen_path = os.path.join(sys_bus_path, "xen")
223+ self.makeFile(path=sys_bus_xen_path, content="foo")
224+
225+ self.mstore.set_accepted_types(["computer-info"])
226+ plugin = ComputerInfo(root_path=root_path)
227+ self.monitor.add(plugin)
228+
229+ plugin.exchange()
230+ message = self.mstore.get_pending_messages()[0]
231+ self.assertEquals('xen', message["vm-info"])
232+
233+ def test_vminfo_is_xen_when_proc_xen_exists(self):
234+ """
235+ L{ComputerInfo} should have 'xen' as value of vm-info when
236+ /proc/xen exists.
237+ """
238+ root_path = self.makeDir()
239+ proc_path = os.path.join(root_path, "proc")
240+ self.makeDir(path=proc_path)
241+
242+ proc_xen_path = os.path.join(proc_path, "xen")
243+ self.makeFile(path=proc_xen_path, content="foo")
244+
245+ self.mstore.set_accepted_types(["computer-info"])
246+ plugin = ComputerInfo(root_path=root_path)
247+ self.monitor.add(plugin)
248+
249+ plugin.exchange()
250+ message = self.mstore.get_pending_messages()[0]
251+ self.assertEquals('xen', message["vm-info"])
252+
253+ def test_vminfo_is_kvm_when_qemu_is_found_in_proc_cpuinfo(self):
254+ """
255+ L{ComputerInfo} should have 'kvm' as value of vm-info when
256+ QEMU Virtual CPU is found in /proc/cpuinfo.
257+ """
258+ root_path = self.makeDir()
259+ proc_path = os.path.join(root_path, "proc")
260+ self.makeDir(path=proc_path)
261+
262+ cpuinfo_path = os.path.join(proc_path, "cpuinfo")
263+ self.makeFile(path=cpuinfo_path, content=self.sample_kvm_cpuinfo)
264+
265+ self.mstore.set_accepted_types(["computer-info"])
266+ plugin = ComputerInfo(root_path=root_path)
267+ self.monitor.add(plugin)
268+
269+ plugin.exchange()
270+ message = self.mstore.get_pending_messages()[0]
271+ self.assertEqual("kvm", message["vm-info"])
272+
273+ def test_vminfo_is_empty_when_qemu_is_not_found_in_proc_cpuinfo(self):
274+ """
275+ L{ComputerInfo} should have an empty string as value of vm-info when
276+ QEMU Virtual CPU is not found in /proc/cpuinfo.
277+ """
278+ root_path = self.makeDir()
279+ proc_path = os.path.join(root_path, "proc")
280+ self.makeDir(path=proc_path)
281+
282+ cpuinfo_path = os.path.join(proc_path, "cpuinfo")
283+ self.makeFile(path=cpuinfo_path, content="foo")
284+
285+ self.mstore.set_accepted_types(["computer-info"])
286+ plugin = ComputerInfo(root_path=root_path)
287+ self.monitor.add(plugin)
288+
289+ plugin.exchange()
290+ message = self.mstore.get_pending_messages()[0]
291+ self.assertEqual("", message["vm-info"])

Subscribers

People subscribed via source and target branches

to all changes: