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
=== modified file 'landscape/message_schemas.py'
--- landscape/message_schemas.py 2010-06-28 12:52:39 +0000
+++ landscape/message_schemas.py 2011-04-20 21:43:28 +0000
@@ -68,10 +68,11 @@
68 "computer-info",68 "computer-info",
69 {"hostname": utf8,69 {"hostname": utf8,
70 "total-memory": Int(),70 "total-memory": Int(),
71 "total-swap": Int()},71 "total-swap": Int(),
72 "vm-info": String()},
72 # Not sure why these are all optional, but it's explicitly tested73 # Not sure why these are all optional, but it's explicitly tested
73 # in the server74 # in the server
74 optional=["hostname", "total-memory", "total-swap"])75 optional=["hostname", "total-memory", "total-swap", "vm-info"])
7576
76DISTRIBUTION_INFO = Message(77DISTRIBUTION_INFO = Message(
77 "distribution-info",78 "distribution-info",
7879
=== modified file 'landscape/monitor/computerinfo.py'
--- landscape/monitor/computerinfo.py 2010-12-10 13:57:11 +0000
+++ landscape/monitor/computerinfo.py 2011-04-20 21:43:28 +0000
@@ -1,4 +1,5 @@
1import logging1import logging
2import os
23
3from landscape.lib.lsb_release import LSB_RELEASE_FILENAME, parse_lsb_release4from landscape.lib.lsb_release import LSB_RELEASE_FILENAME, parse_lsb_release
4from landscape.lib.network import get_fqdn5from landscape.lib.network import get_fqdn
@@ -16,10 +17,12 @@
1617
17 def __init__(self, get_fqdn=get_fqdn,18 def __init__(self, get_fqdn=get_fqdn,
18 meminfo_file="/proc/meminfo",19 meminfo_file="/proc/meminfo",
19 lsb_release_filename=LSB_RELEASE_FILENAME):20 lsb_release_filename=LSB_RELEASE_FILENAME,
21 root_path="/"):
20 self._get_fqdn = get_fqdn22 self._get_fqdn = get_fqdn
21 self._meminfo_file = meminfo_file23 self._meminfo_file = meminfo_file
22 self._lsb_release_filename = lsb_release_filename24 self._lsb_release_filename = lsb_release_filename
25 self._root_path = root_path
2326
24 def register(self, registry):27 def register(self, registry):
25 super(ComputerInfo, self).register(registry)28 super(ComputerInfo, self).register(registry)
@@ -57,6 +60,8 @@
57 self._add_if_new(message, "total-memory",60 self._add_if_new(message, "total-memory",
58 total_memory)61 total_memory)
59 self._add_if_new(message, "total-swap", total_swap)62 self._add_if_new(message, "total-swap", total_swap)
63 vm_info = self._get_vm_info()
64 self._add_if_new(message, "vm-info", vm_info)
60 return message65 return message
6166
62 def _add_if_new(self, message, key, value):67 def _add_if_new(self, message, key, value):
@@ -90,3 +95,37 @@
90 message = {}95 message = {}
91 message.update(parse_lsb_release(self._lsb_release_filename))96 message.update(parse_lsb_release(self._lsb_release_filename))
92 return message97 return message
98
99 def _get_vm_info(self):
100 """
101 This is a utility that returns the virtualization type
102
103 It loops through some possible configurations and return a string with
104 the name of the technology being used or None if there's no match
105 """
106 virt_info = ""
107
108 def join_root_path(path):
109 return os.path.join(self._root_path, path)
110
111 xen_paths = ["proc/sys/xen", "sys/bus/xen", "proc/xen"]
112 xen_paths = map(join_root_path, xen_paths)
113
114 vz_path = os.path.join(self._root_path, "proc/vz")
115 if os.path.exists(vz_path):
116 virt_info = "openvz"
117
118 elif filter(os.path.exists, xen_paths):
119 virt_info = "xen"
120
121 cpu_info_path = os.path.join(self._root_path, "proc/cpuinfo")
122 if os.path.exists(cpu_info_path):
123 try:
124 fd = open(cpu_info_path)
125 cpuinfo = fd.read()
126 if "QEMU Virtual CPU" in cpuinfo:
127 virt_info = "kvm"
128 finally:
129 fd.close()
130
131 return virt_info
93132
=== modified file 'landscape/monitor/tests/test_computerinfo.py'
--- landscape/monitor/tests/test_computerinfo.py 2010-04-28 13:07:53 +0000
+++ landscape/monitor/tests/test_computerinfo.py 2011-04-20 21:43:28 +0000
@@ -1,4 +1,5 @@
1import re1import re
2import os
23
3from landscape.monitor.computerinfo import ComputerInfo4from landscape.monitor.computerinfo import ComputerInfo
4from landscape.tests.helpers import LandscapeTest, MonitorHelper5from landscape.tests.helpers import LandscapeTest, MonitorHelper
@@ -44,6 +45,27 @@
44VmallocChunk: 107432 kB45VmallocChunk: 107432 kB
45"""46"""
4647
48 sample_kvm_cpuinfo = """
49processor : 0
50vendor_id : GenuineIntel
51cpu family : 6
52model : 2
53model name : QEMU Virtual CPU version 0.14.0
54stepping : 3
55cpu MHz : 2653.112
56cache size : 4096 KB
57fpu : yes
58fpu_exception : yes
59cpuid level : 4
60wp : yes
61flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca
62bogomips : 5306.22
63clflush size : 64
64cache_alignment : 64
65address sizes : 40 bits physical, 48 bits virtual
66power management:
67"""
68
47 def setUp(self):69 def setUp(self):
48 LandscapeTest.setUp(self)70 LandscapeTest.setUp(self)
49 self.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)71 self.lsb_release_filename = self.makeFile(SAMPLE_LSB_RELEASE)
@@ -272,14 +294,15 @@
272 meminfo_filename = self.makeFile(self.sample_memory_info)294 meminfo_filename = self.makeFile(self.sample_memory_info)
273 plugin = ComputerInfo(get_fqdn=get_fqdn,295 plugin = ComputerInfo(get_fqdn=get_fqdn,
274 meminfo_file=meminfo_filename,296 meminfo_file=meminfo_filename,
275 lsb_release_filename=self.lsb_release_filename)297 lsb_release_filename=self.lsb_release_filename,
298 root_path=self.makeDir())
276 self.monitor.add(plugin)299 self.monitor.add(plugin)
277 plugin.exchange()300 plugin.exchange()
278 self.reactor.fire("resynchronize")301 self.reactor.fire("resynchronize")
279 plugin.exchange()302 plugin.exchange()
280 computer_info = {"type": "computer-info", "hostname": "ooga.local",303 computer_info = {"type": "computer-info", "hostname": "ooga.local",
281 "timestamp": 0, "total-memory": 1510,304 "timestamp": 0, "total-memory": 1510,
282 "total-swap": 1584}305 "total-swap": 1584, "vm-info": ""}
283 dist_info = {"type": "distribution-info",306 dist_info = {"type": "distribution-info",
284 "code-name": "dapper", "description": "Ubuntu 6.06.1 LTS",307 "code-name": "dapper", "description": "Ubuntu 6.06.1 LTS",
285 "distributor-id": "Ubuntu", "release": "6.06"}308 "distributor-id": "Ubuntu", "release": "6.06"}
@@ -324,3 +347,146 @@
324347
325 self.mstore.set_accepted_types(["distribution-info", "computer-info"])348 self.mstore.set_accepted_types(["distribution-info", "computer-info"])
326 self.assertMessages(list(self.mstore.get_pending_messages()), [])349 self.assertMessages(list(self.mstore.get_pending_messages()), [])
350
351 def test_vminfo_empty_when_no_virtualization_is_found(self):
352 """
353 L{ComputerInfo} should always have the vm-info key even when no
354 virtualization is used.
355
356 And it should be an empty string.
357 """
358 self.mstore.set_accepted_types(["computer-info"])
359 root_path = self.makeDir()
360 plugin = ComputerInfo(root_path=root_path)
361 self.monitor.add(plugin)
362
363 plugin.exchange()
364 message = self.mstore.get_pending_messages()[0]
365 self.assertTrue("vm-info" in message)
366 self.assertEqual("", message["vm-info"])
367
368 def test_vminfo_is_openvz_when_proc_vz_exists(self):
369 """
370 L{ComputerInfo} should have 'openvz' as the value of vm-info when
371 /proc/vz exists.
372 """
373 root_path = self.makeDir()
374 proc_path = os.path.join(root_path, "proc")
375 self.makeDir(path=proc_path)
376
377 proc_vz_path = os.path.join(proc_path, "vz")
378 self.makeFile(path=proc_vz_path, content="foo")
379
380 self.mstore.set_accepted_types(["computer-info"])
381 plugin = ComputerInfo(root_path=root_path)
382 self.monitor.add(plugin)
383
384 plugin.exchange()
385 message = self.mstore.get_pending_messages()[0]
386 self.assertEquals('openvz', message["vm-info"])
387
388 def test_vminfo_is_xen_when_proc_sys_xen_exists(self):
389 """
390 L{ComputerInfo} should have 'xen' as the value of vm-info when
391 /proc/sys/xen exists.
392 """
393 root_path = self.makeDir()
394 proc_path = os.path.join(root_path, "proc")
395 self.makeDir(path=proc_path)
396
397 proc_sys_path = os.path.join(proc_path, "sys")
398 self.makeDir(path=proc_sys_path)
399
400 proc_sys_xen_path = os.path.join(proc_sys_path, "xen")
401 self.makeFile(path=proc_sys_xen_path, content="foo")
402
403 self.mstore.set_accepted_types(["computer-info"])
404 plugin = ComputerInfo(root_path=root_path)
405 self.monitor.add(plugin)
406
407 plugin.exchange()
408 message = self.mstore.get_pending_messages()[0]
409 self.assertEquals('xen', message["vm-info"])
410
411 def test_vminfo_is_xen_when_sys_bus_xen_exists(self):
412 """
413 L{ComputerInfo} should have 'xen' as value of vm-info when
414 /sys/bus/xen exists
415 """
416 root_path = self.makeDir()
417 sys_path = os.path.join(root_path, "sys")
418 self.makeDir(path=sys_path)
419
420 sys_bus_path = os.path.join(sys_path, "bus")
421 self.makeDir(path=sys_bus_path)
422
423 sys_bus_xen_path = os.path.join(sys_bus_path, "xen")
424 self.makeFile(path=sys_bus_xen_path, content="foo")
425
426 self.mstore.set_accepted_types(["computer-info"])
427 plugin = ComputerInfo(root_path=root_path)
428 self.monitor.add(plugin)
429
430 plugin.exchange()
431 message = self.mstore.get_pending_messages()[0]
432 self.assertEquals('xen', message["vm-info"])
433
434 def test_vminfo_is_xen_when_proc_xen_exists(self):
435 """
436 L{ComputerInfo} should have 'xen' as value of vm-info when
437 /proc/xen exists.
438 """
439 root_path = self.makeDir()
440 proc_path = os.path.join(root_path, "proc")
441 self.makeDir(path=proc_path)
442
443 proc_xen_path = os.path.join(proc_path, "xen")
444 self.makeFile(path=proc_xen_path, content="foo")
445
446 self.mstore.set_accepted_types(["computer-info"])
447 plugin = ComputerInfo(root_path=root_path)
448 self.monitor.add(plugin)
449
450 plugin.exchange()
451 message = self.mstore.get_pending_messages()[0]
452 self.assertEquals('xen', message["vm-info"])
453
454 def test_vminfo_is_kvm_when_qemu_is_found_in_proc_cpuinfo(self):
455 """
456 L{ComputerInfo} should have 'kvm' as value of vm-info when
457 QEMU Virtual CPU is found in /proc/cpuinfo.
458 """
459 root_path = self.makeDir()
460 proc_path = os.path.join(root_path, "proc")
461 self.makeDir(path=proc_path)
462
463 cpuinfo_path = os.path.join(proc_path, "cpuinfo")
464 self.makeFile(path=cpuinfo_path, content=self.sample_kvm_cpuinfo)
465
466 self.mstore.set_accepted_types(["computer-info"])
467 plugin = ComputerInfo(root_path=root_path)
468 self.monitor.add(plugin)
469
470 plugin.exchange()
471 message = self.mstore.get_pending_messages()[0]
472 self.assertEqual("kvm", message["vm-info"])
473
474 def test_vminfo_is_empty_when_qemu_is_not_found_in_proc_cpuinfo(self):
475 """
476 L{ComputerInfo} should have an empty string as value of vm-info when
477 QEMU Virtual CPU is not found in /proc/cpuinfo.
478 """
479 root_path = self.makeDir()
480 proc_path = os.path.join(root_path, "proc")
481 self.makeDir(path=proc_path)
482
483 cpuinfo_path = os.path.join(proc_path, "cpuinfo")
484 self.makeFile(path=cpuinfo_path, content="foo")
485
486 self.mstore.set_accepted_types(["computer-info"])
487 plugin = ComputerInfo(root_path=root_path)
488 self.monitor.add(plugin)
489
490 plugin.exchange()
491 message = self.mstore.get_pending_messages()[0]
492 self.assertEqual("", message["vm-info"])

Subscribers

People subscribed via source and target branches

to all changes: