Merge lp:~shawn111/checkbox/hexr-dkms into lp:checkbox

Proposed by Shawn Wang
Status: Merged
Approved by: Zygmunt Krynicki
Approved revision: 3728
Merged at revision: 3778
Proposed branch: lp:~shawn111/checkbox/hexr-dkms
Merge into: lp:checkbox
Diff against target: 688 lines (+603/-0)
8 files modified
providers/plainbox-provider-certification-client/whitelists/client-cert-regression.whitelist (+2/-0)
providers/plainbox-provider-certification-client/whitelists/client-cert.whitelist (+2/-0)
providers/plainbox-provider-certification-client/whitelists/client-selftest-14-04.whitelist (+2/-0)
providers/plainbox-provider-certification-client/whitelists/client-selftest.whitelist (+2/-0)
providers/plainbox-provider-checkbox/bin/dkms_info (+569/-0)
providers/plainbox-provider-checkbox/jobs/info.txt.in (+22/-0)
providers/plainbox-provider-checkbox/whitelists/autotesting.whitelist (+2/-0)
providers/plainbox-provider-checkbox/whitelists/hwsubmit.whitelist (+2/-0)
To merge this branch: bzr merge lp:~shawn111/checkbox/hexr-dkms
Reviewer Review Type Date Requested Status
Zygmunt Krynicki (community) Approve
Daniel Manrique (community) Needs Fixing
Review via email: mp+257533@code.launchpad.net

Description of the change

providers:checkbox: Added dkms_info_attachment job.

$ providers/plainbox-provider-checkbox/bin/dkms_info --format onelines
workaround-xhci-quirk-trusty-dkms_0.8: {'xhci_quirk': ['pci:v0000168Cd00000036sv*sd*bc*sc*i*']}
- oem-audio-hda-daily-dkms_0.201503121632~ubuntu14.04.1: ['pci:v00008086d*sv*sd*bc04sc03i00*', 'pci:v00008086d*sv*sd*bc04sc03i00*', 'pci:v00008086d00000C0Csv*sd*bc*sc*i*', 'pci:v00008086d00008C20sv*sd*bc*sc*i*']

$ providers/plainbox-provider-checkbox/bin/dkms_info --dkms --format dumps
[
    {
        "arch": "x86_64",
        "dkms_name": "hello",
        "dkms_ver": "0.1",
        "install_mods": {},
        "kernel_ver": "3.19.0-15-generic",
        "mods": [
            "hello"
        ],
        "package_name": null
    },
    {
        "arch": "x86_64",
        "dkms_name": "workaround-xhci-quirk-trusty",
        "dkms_ver": "0.8",
        "install_mods": {
            "xhci_quirk": [
                "pci:v0000168Cd00000036sv*sd*bc*sc*i*"
            ]
        },
        "kernel_ver": "3.19.0-15-generic",
        "mods": [
            "xhci_quirk"
        ],
        "package": {
            "architecture": "all",
            "conffiles": "\n /etc/modprobe.d/xhci-quirk.conf cee794e41804e6e3494a1b8f75a8c99e",
            "depends": "dkms (>= 1.95)",
            "description": "workaround-xhci-quirk-trusty driver in DKMS format.",
            "installed-size": "49",
            "maintainer": "Commercial Engineering <email address hidden>",
            "modaliases": "hwe(pci:v0000168Cd00000036sv*sd*bc*sc*i*, pci:v000014E4d*sv*sd*bc*sc*i*)",
            "package": "workaround-xhci-quirk-trusty-dkms",
            "priority": "optional",
            "section": "misc",
            "status": "install ok installed",
            "version": "0.8"
        },
        "package_name": "workaround-xhci-quirk-trusty-dkms"
    }
]

To post a comment you must log in.
Revision history for this message
Daniel Manrique (roadmr) wrote :

Thanks, the information this gives is very complete. I have a few comments, see below. Also, could you please run flake8 and fix the PEP-8 issues? I found 10 of them but it's probably easier if you run flake8 yourself to see what I mean :)

review: Needs Fixing
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

Lots of details below. Thanks for proposing this but please keep iterating until it all gets fixed.

Also, rewrite your commit messages:

<component>:<subcomponent>: imperative statement as to what is being changed
\n
Longer description as to what is being changed and why. Include as much detail as you want here.

Thanks
ZK

review: Needs Fixing
Revision history for this message
Shawn Wang (shawn111) wrote :

@ZK,

I still don't understand how to use flake8-docstrings to check.
It gave me some Error but I have no idea how to fix, I will do it tomorrow.

 * I don't use python3-debian, because I just need the Modaliases information, rfc822 is good for this case
 * when will check_output() raise OSError? >> when dkms is not found

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

To use flake8-docstrings just pip3 install --user flake8-docstrings
and run flake8 on the file you are working on.

The python3-debian module seems to contain the deb822 module which
seems to do the parsing for the Debian variant of RFC822. I would
prefer to use that over the email module as it probably handles
something that the email parser will misinterpret.

Thanks
ZK

On Tue, Apr 28, 2015 at 4:37 PM, Shawn Wang <email address hidden> wrote:
> @ZK,
>
> I still don't understand how to use flake8-docstrings to check.
> It gave me some Error but I have no idea how to fix, I will do it tomorrow.
>
> * I don't use python3-debian, because I just need the Modaliases information, rfc822 is good for this case
> * when will check_output() raise OSError? >> when dkms is not found
>
> --
> https://code.launchpad.net/~shawn111/checkbox/hexr-dkms/+merge/257533
> You are reviewing the proposed merge of lp:~shawn111/checkbox/hexr-dkms into lp:checkbox.

Revision history for this message
Daniel Manrique (roadmr) wrote :

On Tue, Apr 28, 2015 at 10:42 AM, Zygmunt Krynicki
<email address hidden> wrote:
> To use flake8-docstrings just pip3 install --user flake8-docstrings
> and run flake8 on the file you are working on.
>
> The python3-debian module seems to contain the deb822 module which
> seems to do the parsing for the Debian variant of RFC822. I would
> prefer to use that over the email module as it probably handles
> something that the email parser will misinterpret.

If we use this, will we also need to add python3-debian as a
dependency in packaging?

Just asking/pointing out so we're aware of the possible extra work needed

>
> Thanks
> ZK
>
> On Tue, Apr 28, 2015 at 4:37 PM, Shawn Wang <email address hidden> wrote:
>> @ZK,
>>
>> I still don't understand how to use flake8-docstrings to check.
>> It gave me some Error but I have no idea how to fix, I will do it tomorrow.
>>
>> * I don't use python3-debian, because I just need the Modaliases information, rfc822 is good for this case
>> * when will check_output() raise OSError? >> when dkms is not found
>>
>> --
>> https://code.launchpad.net/~shawn111/checkbox/hexr-dkms/+merge/257533
>> You are reviewing the proposed merge of lp:~shawn111/checkbox/hexr-dkms into lp:checkbox.
>
> --
> https://code.launchpad.net/~shawn111/checkbox/hexr-dkms/+merge/257533
> You are reviewing the proposed merge of lp:~shawn111/checkbox/hexr-dkms into lp:checkbox.

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

Yes. I was going to wait until Shawn is done with the current batch.

Shawn, please add this next to the new script (in some .pxu file):

# The dkms_info script requires python3-debian package
unit: packaging meta-data
os-id: debian
Depends: python3-debian

For details check the plainbox-packaging-meta-data manual page.

Thanks
ZK

On Tue, Apr 28, 2015 at 5:21 PM, Daniel Manrique
<email address hidden> wrote:
> On Tue, Apr 28, 2015 at 10:42 AM, Zygmunt Krynicki
> <email address hidden> wrote:
>> To use flake8-docstrings just pip3 install --user flake8-docstrings
>> and run flake8 on the file you are working on.
>>
>> The python3-debian module seems to contain the deb822 module which
>> seems to do the parsing for the Debian variant of RFC822. I would
>> prefer to use that over the email module as it probably handles
>> something that the email parser will misinterpret.
>
> If we use this, will we also need to add python3-debian as a
> dependency in packaging?
>
> Just asking/pointing out so we're aware of the possible extra work needed
>
>>
>> Thanks
>> ZK
>>
>> On Tue, Apr 28, 2015 at 4:37 PM, Shawn Wang <email address hidden> wrote:
>>> @ZK,
>>>
>>> I still don't understand how to use flake8-docstrings to check.
>>> It gave me some Error but I have no idea how to fix, I will do it tomorrow.
>>>
>>> * I don't use python3-debian, because I just need the Modaliases information, rfc822 is good for this case
>>> * when will check_output() raise OSError? >> when dkms is not found
>>>
>>> --
>>> https://code.launchpad.net/~shawn111/checkbox/hexr-dkms/+merge/257533
>>> You are reviewing the proposed merge of lp:~shawn111/checkbox/hexr-dkms into lp:checkbox.
>>
>> --
>> https://code.launchpad.net/~shawn111/checkbox/hexr-dkms/+merge/257533
>> You are reviewing the proposed merge of lp:~shawn111/checkbox/hexr-dkms into lp:checkbox.
>
> --
> https://code.launchpad.net/~shawn111/checkbox/hexr-dkms/+merge/257533
> You are reviewing the proposed merge of lp:~shawn111/checkbox/hexr-dkms into lp:checkbox.

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

It keeps getting better but there are a few more things to fix below.

review: Needs Fixing
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

Hey.

Thanks for all the improvements! There are some more stylistic below, please have a look at them

In general you can cut some of the code away by using @functools.lrucache() for many of the expensive things you compute in properties and manually store to avoid re-computing them later. The second common thing is the way function/method docstrings are formatted.

A "proper docstring looks like this"

def foo(a, b):
   """Do something very simple."""
   return a + b

def bar(a, b):
   """
   Do something more complex to warrant a longer docstring.

   :param a:
       The first argument
   :param b:
       The second argument
   :returns:
       The phase of the moon

   This function computes the phase of the moon, as visible on the *a*th
   day of the *b*th month of the current year. If the current year is a
   Leap year then the constant "rabbit" is returned instead.
   """

This is somewhat more verbose but it is, in general, easier to read. Use your gut feeling to pick the short vs the long format.

I didn't try running this yet. I'll post a separate set of comments for that later today.

review: Needs Fixing
Revision history for this message
Shawn Wang (shawn111) wrote :

> Hey.
>
> Thanks for all the improvements! There are some more stylistic below, please
> have a look at them
>
> In general you can cut some of the code away by using @functools.lrucache()
> for many of the expensive things you compute in properties and manually store
> to avoid re-computing them later. The second common thing is the way
> function/method docstrings are formatted.
>
> A "proper docstring looks like this"
>
>
> def foo(a, b):
> """Do something very simple."""
> return a + b
>
>
> def bar(a, b):
> """
> Do something more complex to warrant a longer docstring.
>
> :param a:
> The first argument
> :param b:
> The second argument
> :returns:
> The phase of the moon
>
> This function computes the phase of the moon, as visible on the *a*th
> day of the *b*th month of the current year. If the current year is a
> Leap year then the constant "rabbit" is returned instead.
> """
>
> This is somewhat more verbose but it is, in general, easier to read. Use your
> gut feeling to pick the short vs the long format.
>
> I didn't try running this yet. I'll post a separate set of comments for that
> later today.

Hi Zyga,

I did some performance improve and update the docstring.
However, "dpkg -S", "modalaises matching" are still need some time.

real 0m1.345s (before) -> 0m0.986s
user 0m0.973s (before) -> 0m0.676s
sys 0m0.072s

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

Please review the comments on the previous iteration of the proposal. You didn't address all of them (lru and uname, for example).

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

16:25 < roadmr> zyga: got time for another quick look at https://code.launchpad.net/~shawn111/checkbox/hexr-dkms/+merge/257533 ? some things may still be missing :(
16:25 <@zyga> roadmr: yeah, looking
16:26 <@zyga> roadmr: oh, I did today I think
16:26 <@zyga> roadmr: let's see
16:28 <@zyga> roadmr: I did review it but not everything is done yet, I know shawnwang didn't know how to use lru_cache but we discussed that on IRC
16:28 <@zyga> roadmr: I'm waiting for another upload that will address all the manual caching
16:28 < roadmr> zyga: ok, not a problem, thanks!

Revision history for this message
Shawn Wang (shawn111) wrote :

@Zyga,

I updated with lru_cache and classmethod.

It didn't improve too much of performance.

There are two bottlenecks: dkms status(run one time), match_patterns(run many times).
Both two bottlenecks are depends on how many device packages are installed.
More packages are more slow.

I run on one Dell machine, it is ~22 secs. (6 packages)
another HP machine, it is ~3 secs. (5 packages)
On my own machine, it is ~2 secs. (3 packages)

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

I worked on this branch today and the results can be seen in the hexr-dkms branch of my git repository [1]

[1] https://code.launchpad.net/~zyga/checkbox/+git/checkbox/+ref/hexr-dkms

There are numerous changes that I've made. I think that docstrings and tests are badly missing in a few spots. The user interface could use a few changes too.

I ran the code on my machine with virtualbox module. I could use test data from systems that have the other useful types of module information (oem-something and patterned modaliases).

Lastly I think the way we collect and present information should change. Dicts are great but python objects are dicts too and they feel more suited for the job. This affects the get_dkmses and get_nondkmses functions.

I've left lots of XXX: notes in the tree, on stuff that I didn't finish fixing. Let's see what we can do about this.

review: Needs Fixing
Revision history for this message
Zygmunt Krynicki (zyga) wrote :
lp:~shawn111/checkbox/hexr-dkms updated
3727. By Shawn Wang

providers:checkbox: add dkms_info to fetch device related package information.

supported package types:
 - dkms (Dynamic Kernel Module Support): provides kernel modules
 - non-dkms: packages that with modaliases header and don't exist in dkms list
  - hardware modalias: might be a dkms or config package
  - oemalias: It is like non-dkms(w/o modalias)

supported output format:
 - summary: one line per packages with matched modaliases information
 - json: json output (fully information)

3728. By Shawn Wang

providers:checkbox: add dkms_info_attachment, device_package_info_attachment jobs

Collect device related package information,
 dkms_info_attachment: json dump format of dkms package information
 device_package_info_attachment: list packages with matched hardware modaliases information

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

Let's merge it.

Thanks a *lot* for iterating and for changing this so many times. I think we can iron out any remaining issue, -- if any -- as bugs.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'providers/plainbox-provider-certification-client/whitelists/client-cert-regression.whitelist'
2--- providers/plainbox-provider-certification-client/whitelists/client-cert-regression.whitelist 2015-04-24 20:58:54 +0000
3+++ providers/plainbox-provider-certification-client/whitelists/client-cert-regression.whitelist 2015-05-14 11:56:31 +0000
4@@ -130,6 +130,8 @@
5 sysfs_attachment
6 udev_attachment
7 lsmod_attachment
8+device_package_info_attachment
9+dkms_info_attachment
10 acpi_sleep_attachment
11 info/touchpad_driver
12 info/disk_partitions
13
14=== modified file 'providers/plainbox-provider-certification-client/whitelists/client-cert.whitelist'
15--- providers/plainbox-provider-certification-client/whitelists/client-cert.whitelist 2015-05-14 06:40:17 +0000
16+++ providers/plainbox-provider-certification-client/whitelists/client-cert.whitelist 2015-05-14 11:56:31 +0000
17@@ -412,6 +412,8 @@
18 sysfs_attachment
19 udev_attachment
20 lsmod_attachment
21+device_package_info_attachment
22+dkms_info_attachment
23 acpi_sleep_attachment
24 info/hdparm
25 info/hdparm_.*.txt
26
27=== modified file 'providers/plainbox-provider-certification-client/whitelists/client-selftest-14-04.whitelist'
28--- providers/plainbox-provider-certification-client/whitelists/client-selftest-14-04.whitelist 2015-04-29 13:45:16 +0000
29+++ providers/plainbox-provider-certification-client/whitelists/client-selftest-14-04.whitelist 2015-05-14 11:56:31 +0000
30@@ -360,6 +360,8 @@
31 sysfs_attachment
32 udev_attachment
33 lsmod_attachment
34+device_package_info_attachment
35+dkms_info_attachment
36 acpi_sleep_attachment
37 info/hdparm
38 info/hdparm_.*.txt
39
40=== modified file 'providers/plainbox-provider-certification-client/whitelists/client-selftest.whitelist'
41--- providers/plainbox-provider-certification-client/whitelists/client-selftest.whitelist 2015-04-29 13:45:16 +0000
42+++ providers/plainbox-provider-certification-client/whitelists/client-selftest.whitelist 2015-05-14 11:56:31 +0000
43@@ -360,6 +360,8 @@
44 sysfs_attachment
45 udev_attachment
46 lsmod_attachment
47+device_package_info_attachment
48+dkms_info_attachment
49 acpi_sleep_attachment
50 info/hdparm
51 info/hdparm_.*.txt
52
53=== added file 'providers/plainbox-provider-checkbox/bin/dkms_info'
54--- providers/plainbox-provider-checkbox/bin/dkms_info 1970-01-01 00:00:00 +0000
55+++ providers/plainbox-provider-checkbox/bin/dkms_info 2015-05-14 11:56:31 +0000
56@@ -0,0 +1,569 @@
57+#!/usr/bin/env python3
58+# encoding: utf-8
59+# Copyright 2015 Canonical Ltd.
60+# Written by:
61+# Shawn Wang <shawn.wang@canonical.com>
62+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
63+#
64+# This program is free software: you can redistribute it and/or modify
65+# it under the terms of the GNU General Public License version 3,
66+# as published by the Free Software Foundation.
67+#
68+# This program is distributed in the hope that it will be useful,
69+# but WITHOUT ANY WARRANTY; without even the implied warranty of
70+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
71+# GNU General Public License for more details.
72+#
73+# You should have received a copy of the GNU General Public License
74+# along with this program. If not, see <http://www.gnu.org/licenses/>.
75+
76+"""
77+dkms_info provides device package information.
78+
79+supported package types:
80+ - dkms (Dynamic Kernel Module Support): provides kernel modules
81+ - non-dkms: packages that with modaliases header and don't exist in dkms list
82+ - hardware modalias: might be unused dkms or config package
83+ - oemalias: It is like non-dkms(w/o modalias)
84+supported output format:
85+ - onelines: one line per packages with matched modaliases information
86+ - dumps: json output (fully information)
87+"""
88+
89+import fnmatch
90+import functools
91+import email.parser
92+import io
93+import json
94+import logging
95+import os
96+import subprocess
97+import sys
98+import unittest
99+
100+from guacamole import Command
101+
102+try:
103+ from unittest import mock
104+except ImportError:
105+ from plainbox.vendor import mock
106+
107+_logger = logging.getLogger(None)
108+
109+
110+@functools.lru_cache()
111+def get_system_module_list():
112+ """
113+ Use lsmod to list current kernel modules.
114+
115+ :returns:
116+ A list of module names that are loaded into the current kernel.
117+ """
118+ _logger.info("Looking at inserted kernel modules")
119+ modules = []
120+ with io.open("/proc/modules", 'rt',
121+ encoding='UTF-8') as stream:
122+ for line in stream.readlines():
123+ modules.append(line.split()[0].strip())
124+ return modules
125+
126+
127+@functools.lru_cache()
128+def get_system_modaliases():
129+ r"""
130+ List machine modaliases.
131+
132+ :returns:
133+ dict of hardware modaliases.
134+ key: modalias_type
135+ value: list of modalias_string
136+ """
137+ _logger.info("Looking for modalias files in /sys/devices")
138+
139+ result = {}
140+ name = "modalias"
141+ for root, dirs, files in os.walk("/sys/devices/"):
142+ if name in files:
143+ with io.open(os.path.join(root, name), 'rt',
144+ encoding='UTF-8') as stream:
145+ data = stream.read().strip()
146+ (modalias_type, modalias_string) = data.split(":", 1)
147+
148+ if modalias_type not in result:
149+ result[modalias_type] = []
150+ if modalias_string not in result[modalias_type]:
151+ result[modalias_type].append(modalias_string)
152+ return result
153+
154+
155+def get_installed_dkms_modules():
156+ """
157+ Query dkms_status from /var/lib/dkms/.
158+
159+ An installed dkms module has the below directory.
160+ /var/lib/dkms/<dkms_name>/<dkms_ver>/<kernel_ver>
161+
162+ :returns:
163+ list of (<dkms_name>, <dkms_ver>)
164+ """
165+ _dkmses = []
166+ _logger.info("Querying dkms database")
167+
168+ path = "/var/lib/dkms/"
169+ for root, dirs, files in os.walk(path):
170+ if os.uname().release in dirs:
171+ dkms = root[len(path):].split("/")
172+ if len(dkms) != 2:
173+ continue
174+ _dkmses.append(dkms)
175+ return _dkmses
176+
177+
178+@functools.lru_cache()
179+def match_patterns(patterns):
180+ """
181+ Check modalias patterns matched with modalias, or type is oemalias.
182+
183+ oemalias is a special pattern_type for oem.
184+ :param patterns:
185+ list of modalias pattern from debian package.
186+ :returns:
187+ list of modalias pattern matched with hardware modalias
188+ """
189+ _logger.info("Looking for modalias objects matching")
190+ matched = []
191+ if not patterns:
192+ return matched
193+ hw_modaliases = get_system_modaliases()
194+ for pattern in patterns:
195+ pattern_array = pattern.split(":", 1)
196+ if len(pattern_array) < 2:
197+ _logger.info("skip pattern {}, can't find type".format(pattern))
198+ continue
199+
200+ (pattern_type, pattern_string) = pattern_array
201+
202+ if pattern_type == "oemalias":
203+ matched.append(pattern)
204+ if pattern_type not in hw_modaliases:
205+ continue
206+ for item in hw_modaliases[pattern_type]:
207+ if fnmatch.fnmatch(item, pattern_string):
208+ matched.append(pattern)
209+ return matched
210+
211+
212+class SystemInfoTests(unittest.TestCase):
213+
214+ """Tests for System Information Parsing and Collection."""
215+
216+ _proc_modules = """\
217+xt_REDIRECT 16384 3 - Live 0x0000000000000000
218+nf_nat_redirect 16384 1 xt_REDIRECT, Live 0x0000000000000000
219+xt_hl 16384 3 - Live 0x0000000000000000
220+hid_generic 16384 0 - Live 0x0000000000000000
221+usbhid 53248 0 - Live 0x0000000000000000
222+hid 110592 2 hid_generic,usbhid, Live 0x0000000000000000
223+overlay 45056 1 - Live 0x0000000000000000
224+"""
225+ _modalias = """\
226+usb:v1D6Bp0003d0319dc09dsc00dp03ic09isc00ip00in00
227+"""
228+
229+ def setUp(self):
230+ """Common setup code."""
231+ get_system_module_list.cache_clear()
232+ get_system_modaliases.cache_clear()
233+
234+ @mock.patch('io.open', mock.mock_open(read_data=_proc_modules))
235+ def test_get_module_list__calls_and_parses_lsmod(self):
236+ """Ensure that get_module_list() parses lsmod output."""
237+ # NOTE: Return value was loaded from my system running kernel 4.0.
238+ # The first few and last rows to be precise.
239+ modules = get_system_module_list()
240+ self.assertEqual(modules, [
241+ 'xt_REDIRECT', 'nf_nat_redirect', 'xt_hl', 'hid_generic',
242+ 'usbhid', 'hid', 'overlay'])
243+
244+ @mock.patch('io.open', mock.mock_open(read_data=_proc_modules))
245+ def test_get_module_list_is_cached(self):
246+ """Ensure that get_module_list() cache works."""
247+ modules1 = get_system_module_list()
248+ modules2 = get_system_module_list()
249+ self.assertIn('xt_REDIRECT', modules1)
250+ self.assertIn('overlay', modules2)
251+ self.assertEqual(modules1, modules2)
252+
253+ @mock.patch('os.walk')
254+ @mock.patch('io.open', mock.mock_open(read_data=_modalias))
255+ def test_get_system_modalias(self, mock_os_walk):
256+ """test_get_system_modalias."""
257+ mock_os_walk.return_value = [
258+ ("/sys/devices/pci0000:00/0000:00:14.0/usb2/2-0:1.0/modalias",
259+ ["driver", "subsystem"],
260+ ["modalias", "uevent"]),
261+ ]
262+
263+ """fetch hw_modaliases from machine and check modalis types."""
264+ modaliases = get_system_modaliases()
265+ self.assertEqual(len(modaliases), 1)
266+ self.assertIn("usb", modaliases)
267+
268+ @mock.patch('os.uname')
269+ @mock.patch('os.walk')
270+ def test_get_installed_dkms_modules(self, mock_os_walk, mock_os_uname):
271+ """test_get_installed_dkms_modules."""
272+ mock_os_walk.return_value = [
273+ ("/var/lib/dkms/hello/0.1",
274+ ["3.19.0-15-generic", "build", "source"],
275+ []),
276+ ]
277+ o = mock.Mock()
278+ o.release = "3.19.0-15-generic"
279+ mock_os_uname.return_value = o
280+ self.assertEqual([['hello', '0.1']],
281+ get_installed_dkms_modules())
282+
283+ @mock.patch('__main__.get_system_modaliases')
284+ def test_match_patterns(self, mock_get_system_modaliases):
285+ """Test of match_patterns."""
286+ mock_get_system_modaliases.return_value = {
287+ "pci": ["v0000168Cd00000036sv0000103Csd0000217Fbc02sc80i00",
288+ "v00008086d00008C26sv0000103Csd000022D9bc0Csc03i20"],
289+ "usb": ["v8087p8000d0005dc09dsc00dp01ic09isc00ip00in00",
290+ "v1D6Bp0002d0319dc09dsc00dp00ic09isc00ip00in00"],
291+ }
292+ pkg_modalieses = ["pci:v00008086d00008C26sv*sd*bc*sc*i*",
293+ "usb:v07B4p010Ad0102dc*dsc*dp*ic*isc*ip*in*",
294+ "oemalias:test"]
295+ matched_modalieses = match_patterns(tuple(pkg_modalieses))
296+ # match_patterns
297+ self.assertIn("pci:v00008086d00008C26sv*sd*bc*sc*i*",
298+ matched_modalieses)
299+ self.assertIn("oemalias:test",
300+ matched_modalieses)
301+ self.assertNotIn("usb:v07B4p010Ad0102dc*dsc*dp*ic*isc*ip*in*",
302+ matched_modalieses)
303+
304+
305+class DkmsPackage(object):
306+
307+ """
308+ Handle DKMS type device package, DKMS is a kernel module framework.
309+
310+ It generate modules for installed kernel or different kernel versions.
311+ The dkms modules will be copied to /lib/modulesa/`uname -r`/updates/dkms/.
312+ Those modules might be load by modaliases information.
313+ """
314+
315+ def __init__(self, name, version):
316+ """
317+ init of DkmsPackage, define all the attribute.
318+
319+ :param name:
320+ DKMS module name
321+ :param version:
322+ DKMS module version
323+ """
324+ self.dkms_name = name
325+ self.dkms_ver = version
326+ self.pkg_name = self._query_package()
327+ self.kernel_ver = os.uname().release
328+ self.arch = os.uname().machine
329+ self.mods = self._list_modules()
330+ self.install_mods = self._list_install_modules()
331+ self.pkg = None
332+
333+ def _query_package(self):
334+ """
335+ Query debian package of dkms.
336+
337+ Use dpkg -S to check dkms src path of debian package.
338+
339+ :return:
340+ string of package name or None
341+ """
342+ path = "/usr/src/{}-{}/dkms.conf".format(self.dkms_name, self.dkms_ver)
343+ _logger.info("Looking for packages that provide: %s", path)
344+ dpkg_info_root = "/var/lib/dpkg/info"
345+ for fn in os.listdir(dpkg_info_root):
346+ if not fn.endswith(".list"):
347+ continue
348+ with io.open(os.path.join(dpkg_info_root, fn), 'rt',
349+ encoding='UTF-8') as stream:
350+ if path in stream.read():
351+ return fn[:-len(".list")]
352+ return None
353+
354+ def _list_modules(self):
355+ """
356+ List all the kernel modules that provide by the dkms package.
357+
358+ Module name (.ko) with "-" will be replace to "_" when module loaded.
359+
360+ :param path:
361+ The directory to look at.
362+ :return:
363+ List of kernel modules
364+ """
365+ path = "/var/lib/dkms/{}/{}/{}/{}/module".format(
366+ self.dkms_name, self.dkms_ver, self.kernel_ver, self.arch)
367+ _logger.info("Looking for kernel modules in %s", path)
368+ result = []
369+ for module_file in os.listdir(path):
370+ (module, extension) = os.path.splitext(module_file)
371+ if extension == ".ko":
372+ result.append(module.replace("-", "_"))
373+ return result
374+
375+ def _list_install_modules(self):
376+ """
377+ Return a dict of install_modules.
378+
379+ key is installed module name
380+ value is tuple of matched patterns
381+
382+ :return:
383+ Dict of installed dkms modules
384+ """
385+ install_mods = {}
386+ for m in self.mods:
387+ if m not in get_system_module_list():
388+ continue
389+ _logger.info("Inspecting module %s", m)
390+
391+ output = subprocess.check_output(["modinfo", m],
392+ universal_newlines=True)
393+ aliases = []
394+ for line in output.splitlines():
395+ if not line.startswith("alias:"):
396+ continue
397+
398+ key, value = line.split(':', 1)
399+ aliases.append(value.strip())
400+
401+ install_mods[m] = match_patterns(tuple(aliases))
402+ return install_mods
403+
404+def _headers_to_dist(pkg_str):
405+ """
406+ Convert rft822 headers string to dict.
407+
408+ :param headers:
409+ deb822 headers object
410+ :return:
411+ dict, the key is lowercase of deb822 headers key
412+ """
413+
414+ header = email.parser.Parser().parsestr(pkg_str)
415+ target = {}
416+ for key in header.keys():
417+ target[key.lower()] = header[key]
418+ return target
419+
420+class DebianPackageHandler(object):
421+
422+ """Use rtf822(email) to handle the package information from file_object."""
423+
424+ def __init__(self, extra_pkgs=[], file_object=None):
425+ """
426+ DebianPackageHandler.
427+
428+ :param file_object:
429+ default file open from /var/lib/dpkg/status,
430+ where stored system package information
431+ """
432+ if file_object is None:
433+ file_object = io.open('/var/lib/dpkg/status', 'rt',
434+ encoding='UTF-8')
435+ self._file_object = file_object
436+ self.extra_pkgs = extra_pkgs
437+ self.pkgs = self._get_device_pkgs()
438+
439+ def _gen_all_pkg_strs(self):
440+ """
441+ Get package information in /var/lib/dpkg/status.
442+
443+ :returns:
444+ A generator of debian package.
445+ """
446+ _logger.info("Loading information about all packages")
447+ for pkg_str in self._file_object.read().split('\n\n'):
448+ yield pkg_str
449+
450+
451+ def _get_device_pkgs(self):
452+ """
453+ Only device packages have debian package header 'Modaliases'.
454+
455+ This method get packages with the key ``modaliases``.
456+ Use the method instead of get_all_pkgs for performance issues.
457+
458+ :returns:
459+ A list of dict , the dict is converted from debian package header.
460+ """
461+
462+ _logger.info("Looking for packages providing modaliases")
463+ _modalias_pkgs = {}
464+ target_str = ""
465+ result = {}
466+
467+ for pkg_str in self._gen_all_pkg_strs():
468+
469+ for pkg in self.extra_pkgs:
470+ if pkg.pkg_name is None:
471+ continue
472+ pstr = "Package: {}".format(pkg.pkg_name)
473+ if pstr in pkg_str:
474+ _logger.info("Gathering information of package, {}".format(
475+ pkg.pkg_name))
476+ pkg.pkg = _headers_to_dist(pkg_str)
477+ break
478+ else:
479+ if "Modaliases:" in pkg_str:
480+ pkg = _headers_to_dist(pkg_str)
481+
482+ (modalias_header, pattern_str) = \
483+ pkg['modaliases'].strip(")").split("(")
484+ patterns = pattern_str.split(', ')
485+ patterns.sort()
486+ pkg['match_patterns'] = match_patterns(tuple(patterns))
487+
488+ with io.open("/var/lib/dpkg/info/{}.list".format(pkg['package']),
489+ 'rt', encoding='UTF-8') as stream:
490+ if "/dkms.conf" in stream.read():
491+ pkg['unused_dkms'] = True
492+ result[pkg['package']] = pkg
493+ return result
494+
495+ def to_json(self):
496+ return json.dumps({ "dkms": self.extra_pkgs, "non-dkms": self.pkgs }, default=lambda o: o.__dict__, sort_keys=True, indent=4)
497+
498+ def to_outline(self):
499+ result = ""
500+ for pkg in self.extra_pkgs:
501+ if pkg.pkg is None:
502+ continue
503+ result = "{}\n{}_{}: {}".format(
504+ result,
505+ pkg.pkg_name,
506+ pkg.pkg["version"],
507+ pkg.install_mods)
508+ for pkg_name, pkg in self.pkgs.items():
509+ extra_str = ""
510+ if "unused_dkms" in pkg:
511+ extra_str = "- "
512+ result = "{}\n{}{}_{}: {}".format(
513+ result,
514+ extra_str,
515+ pkg_name,
516+ pkg["version"],
517+ pkg['match_patterns'])
518+ return result
519+
520+
521+class DebianPackageHandlerTest(unittest.TestCase):
522+
523+ """Test of DebianPackageHandler."""
524+
525+ _var_lib_dpkg_status = """\
526+Package: foo
527+Status: install ok installed
528+Modaliases: hwe(pci:v000099DDd00000036sv*sd*bc*sc*i*)
529+
530+Package: foo1
531+Status: install ok installed
532+Modaliases: hwe(pci:v0000AADDd00000036sv*sd*bc*sc*i*)
533+
534+Package: foo2
535+Status: install ok installed
536+
537+Package: foo3
538+Status: install ok installed
539+
540+Package: bar
541+Status: install ok installed
542+
543+"""
544+
545+
546+ @mock.patch('io.open', mock.mock_open(read_data=_var_lib_dpkg_status))
547+ @mock.patch('__main__.get_system_modaliases')
548+ def test_get_pkgs(self, mock_get_system_modaliases):
549+ """Test of test_get_pkgs."""
550+ mock_get_system_modaliases.return_value = {
551+ "pci": ["v0000168Cd00000036sv0000103Csd0000217Fbc02sc80i00",
552+ "v00008086d00008C26sv0000103Csd000022D9bc0Csc03i20"],
553+ "usb": ["v8087p8000d0005dc09dsc00dp01ic09isc00ip00in00",
554+ "v1D6Bp0002d0319dc09dsc00dp00ic09isc00ip00in00"],
555+ }
556+
557+ self.pkg_handler = DebianPackageHandler(
558+ file_object=io.StringIO(self._var_lib_dpkg_status))
559+ self.assertEqual(len(self.pkg_handler.pkgs), 2)
560+
561+
562+class DeviceInfo(Command):
563+
564+ """
565+ Implementation of the dkms-info command.
566+
567+ dkms_info provides device package information.
568+
569+ @EPILOG@
570+
571+ supported package types:
572+ - dkms (Dynamic Kernel Module Support): provides kernel modules
573+ - non-dkms: packages that with modaliases header and don't exist in dkms
574+ list
575+ - hardware modalias: might be unused dkms or config package
576+ - oemalias: It is like non-dkms(w/o modalias)
577+
578+ supported output formats:
579+ - onelines: one line per packages with matched modaliases information
580+ - dumps: json output (fully information)
581+ """
582+
583+ def register_arguments(self, parser):
584+ """Register command line arguments for dkms-info."""
585+
586+ parser.add_argument(
587+ '--format', default="onelines",
588+ choices=["summary", "json"],
589+ help=("Choose output format type: "
590+ "summary (one line per packages) "
591+ "or json (json format, fully information)"))
592+
593+ parser.add_argument(
594+ '--output', default=None,
595+ help=("Output filename to store the output date"))
596+
597+ def invoked(self, ctx):
598+ """Invoke dkms-info."""
599+ logging.basicConfig(
600+ level=logging.INFO, format='[%(relativeCreated)06dms] %(message)s')
601+ _logger.info("Started")
602+
603+ dkms_pkgs = []
604+ for (dkms_name, dkms_ver) in get_installed_dkms_modules():
605+ dkms_pkg = DkmsPackage(dkms_name, dkms_ver)
606+ dkms_pkgs.append(dkms_pkg)
607+
608+ output = sys.stdout
609+ if ctx.args.output is not None:
610+ output = open(ctx.args.output, 'wt', encoding='UTF-8')
611+
612+ pkg_handler = DebianPackageHandler(extra_pkgs=dkms_pkgs)
613+ if ctx.args.format == "summary":
614+ output.write(pkg_handler.to_outline())
615+ else:
616+ output.write(pkg_handler.to_json())
617+ _logger.info("Data collected")
618+
619+
620+if __name__ == '__main__':
621+ if '--test' in sys.argv:
622+ sys.argv.remove('--test')
623+ unittest.main()
624+ else:
625+ DeviceInfo().main()
626
627=== modified file 'providers/plainbox-provider-checkbox/jobs/info.txt.in'
628--- providers/plainbox-provider-checkbox/jobs/info.txt.in 2015-04-29 13:44:43 +0000
629+++ providers/plainbox-provider-checkbox/jobs/info.txt.in 2015-05-14 11:56:31 +0000
630@@ -1,3 +1,8 @@
631+# The dkms_info script requires python3-debian package
632+unit: packaging meta-data
633+os-id: debian
634+Depends: python3-debian
635+
636 id: codecs_attachment
637 plugin: attachment
638 requires: device.driver == 'snd_hda_intel'
639@@ -147,6 +152,23 @@
640 estimated_duration: 0.5
641 _description: Attaches a list of the currently running kernel modules.
642
643+id: device_package_info_attachment
644+plugin: attachment
645+command: dkms_info --format onelines
646+_description: Attaches a list of package with device matched information.
647+ included package types:
648+ - dkms (Dynamic Kernel Module Support): provides kernel modules
649+ - non-dkms: packages that with modaliases header and don't exist in dkms list
650+ - hardware modalias: might be unused dkms or config package
651+ - oemalias: It is like non-dkms(w/o modalias)
652+_summary: Attaches a list of package with device matched information.
653+
654+id: dkms_info_attachment
655+plugin: attachment
656+command: dkms_info --dkms --format dumps
657+_description: Attaches json dumps of installed dkms package information.
658+_summary: Attaches json dumps of installed dkms package information.
659+
660 plugin: attachment
661 id: acpi_sleep_attachment
662 command: [ -e /proc/acpi/sleep ] && cat /proc/acpi/sleep || echo "No /proc/acpi/sleep found"
663
664=== modified file 'providers/plainbox-provider-checkbox/whitelists/autotesting.whitelist'
665--- providers/plainbox-provider-checkbox/whitelists/autotesting.whitelist 2015-04-29 13:45:16 +0000
666+++ providers/plainbox-provider-checkbox/whitelists/autotesting.whitelist 2015-05-14 11:56:31 +0000
667@@ -26,6 +26,8 @@
668 sysfs_attachment
669 udev_attachment
670 lsmod_attachment
671+device_package_info_attachment
672+dkms_info_attachment
673 #Automated Test-verification Jobs
674 __audio__
675 audio/alsa_record_playback_automated
676
677=== modified file 'providers/plainbox-provider-checkbox/whitelists/hwsubmit.whitelist'
678--- providers/plainbox-provider-checkbox/whitelists/hwsubmit.whitelist 2015-04-29 13:45:16 +0000
679+++ providers/plainbox-provider-checkbox/whitelists/hwsubmit.whitelist 2015-05-14 11:56:31 +0000
680@@ -17,6 +17,8 @@
681 dmidecode_attachment
682 kernel_cmdline_attachment
683 lsmod_attachment
684+device_package_info_attachment
685+dkms_info_attachment
686 lspci_attachment
687 lspci_standard_config_attachment
688 modinfo_attachment

Subscribers

People subscribed via source and target branches