Merge lp:~james-page/charms/trusty/mongodb/ch-resync-newton into lp:charms/trusty/mongodb
- Trusty Tahr (14.04)
- ch-resync-newton
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 88 |
Proposed branch: | lp:~james-page/charms/trusty/mongodb/ch-resync-newton |
Merge into: | lp:charms/trusty/mongodb |
Diff against target: |
536 lines (+223/-61) 5 files modified
charmhelpers/contrib/python/packages.py (+22/-7) charmhelpers/core/hookenv.py (+31/-0) charmhelpers/core/host.py (+159/-53) charmhelpers/fetch/__init__.py (+8/-0) charmhelpers/fetch/giturl.py (+3/-1) |
To merge this branch: | bzr merge lp:~james-page/charms/trusty/mongodb/ch-resync-newton |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Review Queue (community) | automated testing | Needs Fixing | |
Ryan Beisner (community) | Approve | ||
Review via email: mp+297046@code.launchpad.net |
Commit message
Description of the change
Resync charm-helpers to ensure that charm understands the Newton UCA.
uosci-testing-bot (uosci-testing-bot) wrote : | # |
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #2174 mongodb for james-page mp297046
UNIT OK: passed
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #779 mongodb for james-page mp297046
AMULET FAIL: amulet-test failed
AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.
Full amulet test output: http://
Build: http://
Ryan Beisner (1chb1n) wrote : | # |
I believe the amulet test failure is an existing problem with the 03_deploy_
00:30:46.446 juju-test.conductor DEBUG : State for 1.25.5: started
00:30:46.447 juju-test.
00:30:47.717 2016-06-10 11:34:08 Starting deployment of osci-sv08
00:30:48.010 2016-06-10 11:34:08 Deploying services...
00:30:48.081 2016-06-10 11:34:08 Deploying service mongodb using /tmp/charmn47ka
00:34:24.871 2016-06-10 11:37:45 Adding relations...
00:34:24.950 2016-06-10 11:37:45 Exposing service 'mongodb'
00:34:25.167 2016-06-10 11:37:45 Deployment complete in 217.69 seconds
00:35:05.382 juju-test.
00:35:05.382
00:35:05.382 juju-test.
00:35:05.382 juju-test.
00:35:05.382 juju-test.conductor INFO : Breaking here as requested by --set-e
00:35:05.382 juju-test INFO : Results: 3 passed, 1 failed, 0 errored
00:35:05.397 make: *** [functional_test] Error 1
Review Queue (review-queue) wrote : | # |
This item has failed automated testing! Results available here http://
Preview Diff
1 | === modified file 'charmhelpers/contrib/python/packages.py' |
2 | --- charmhelpers/contrib/python/packages.py 2016-01-11 18:16:28 +0000 |
3 | +++ charmhelpers/contrib/python/packages.py 2016-06-10 10:55:43 +0000 |
4 | @@ -19,20 +19,35 @@ |
5 | |
6 | import os |
7 | import subprocess |
8 | +import sys |
9 | |
10 | from charmhelpers.fetch import apt_install, apt_update |
11 | from charmhelpers.core.hookenv import charm_dir, log |
12 | |
13 | -try: |
14 | - from pip import main as pip_execute |
15 | -except ImportError: |
16 | - apt_update() |
17 | - apt_install('python-pip') |
18 | - from pip import main as pip_execute |
19 | - |
20 | __author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>" |
21 | |
22 | |
23 | +def pip_execute(*args, **kwargs): |
24 | + """Overriden pip_execute() to stop sys.path being changed. |
25 | + |
26 | + The act of importing main from the pip module seems to cause add wheels |
27 | + from the /usr/share/python-wheels which are installed by various tools. |
28 | + This function ensures that sys.path remains the same after the call is |
29 | + executed. |
30 | + """ |
31 | + try: |
32 | + _path = sys.path |
33 | + try: |
34 | + from pip import main as _pip_execute |
35 | + except ImportError: |
36 | + apt_update() |
37 | + apt_install('python-pip') |
38 | + from pip import main as _pip_execute |
39 | + _pip_execute(*args, **kwargs) |
40 | + finally: |
41 | + sys.path = _path |
42 | + |
43 | + |
44 | def parse_options(given, available): |
45 | """Given a set of options, check if available""" |
46 | for key, value in sorted(given.items()): |
47 | |
48 | === modified file 'charmhelpers/core/hookenv.py' |
49 | --- charmhelpers/core/hookenv.py 2016-01-11 18:16:28 +0000 |
50 | +++ charmhelpers/core/hookenv.py 2016-06-10 10:55:43 +0000 |
51 | @@ -912,6 +912,24 @@ |
52 | subprocess.check_call(cmd) |
53 | |
54 | |
55 | +@translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
56 | +def resource_get(name): |
57 | + """used to fetch the resource path of the given name. |
58 | + |
59 | + <name> must match a name of defined resource in metadata.yaml |
60 | + |
61 | + returns either a path or False if resource not available |
62 | + """ |
63 | + if not name: |
64 | + return False |
65 | + |
66 | + cmd = ['resource-get', name] |
67 | + try: |
68 | + return subprocess.check_output(cmd).decode('UTF-8') |
69 | + except subprocess.CalledProcessError: |
70 | + return False |
71 | + |
72 | + |
73 | @cached |
74 | def juju_version(): |
75 | """Full version string (eg. '1.23.3.1-trusty-amd64')""" |
76 | @@ -976,3 +994,16 @@ |
77 | for callback, args, kwargs in reversed(_atexit): |
78 | callback(*args, **kwargs) |
79 | del _atexit[:] |
80 | + |
81 | + |
82 | +@translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
83 | +def network_get_primary_address(binding): |
84 | + ''' |
85 | + Retrieve the primary network address for a named binding |
86 | + |
87 | + :param binding: string. The name of a relation of extra-binding |
88 | + :return: string. The primary IP address for the named binding |
89 | + :raise: NotImplementedError if run on Juju < 2.0 |
90 | + ''' |
91 | + cmd = ['network-get', '--primary-address', binding] |
92 | + return subprocess.check_output(cmd).strip() |
93 | |
94 | === modified file 'charmhelpers/core/host.py' |
95 | --- charmhelpers/core/host.py 2016-01-11 18:16:28 +0000 |
96 | +++ charmhelpers/core/host.py 2016-06-10 10:55:43 +0000 |
97 | @@ -30,6 +30,8 @@ |
98 | import string |
99 | import subprocess |
100 | import hashlib |
101 | +import functools |
102 | +import itertools |
103 | from contextlib import contextmanager |
104 | from collections import OrderedDict |
105 | |
106 | @@ -126,22 +128,31 @@ |
107 | return subprocess.call(cmd) == 0 |
108 | |
109 | |
110 | +_UPSTART_CONF = "/etc/init/{}.conf" |
111 | +_INIT_D_CONF = "/etc/init.d/{}" |
112 | + |
113 | + |
114 | def service_running(service_name): |
115 | """Determine whether a system service is running""" |
116 | if init_is_systemd(): |
117 | return service('is-active', service_name) |
118 | else: |
119 | - try: |
120 | - output = subprocess.check_output( |
121 | - ['service', service_name, 'status'], |
122 | - stderr=subprocess.STDOUT).decode('UTF-8') |
123 | - except subprocess.CalledProcessError: |
124 | - return False |
125 | - else: |
126 | - if ("start/running" in output or "is running" in output): |
127 | - return True |
128 | - else: |
129 | + if os.path.exists(_UPSTART_CONF.format(service_name)): |
130 | + try: |
131 | + output = subprocess.check_output( |
132 | + ['status', service_name], |
133 | + stderr=subprocess.STDOUT).decode('UTF-8') |
134 | + except subprocess.CalledProcessError: |
135 | return False |
136 | + else: |
137 | + # This works for upstart scripts where the 'service' command |
138 | + # returns a consistent string to represent running 'start/running' |
139 | + if "start/running" in output: |
140 | + return True |
141 | + elif os.path.exists(_INIT_D_CONF.format(service_name)): |
142 | + # Check System V scripts init script return codes |
143 | + return service('status', service_name) |
144 | + return False |
145 | |
146 | |
147 | def service_available(service_name): |
148 | @@ -160,13 +171,13 @@ |
149 | |
150 | |
151 | def init_is_systemd(): |
152 | + """Return True if the host system uses systemd, False otherwise.""" |
153 | return os.path.isdir(SYSTEMD_SYSTEM) |
154 | |
155 | |
156 | def adduser(username, password=None, shell='/bin/bash', system_user=False, |
157 | - primary_group=None, secondary_groups=None): |
158 | - """ |
159 | - Add a user to the system. |
160 | + primary_group=None, secondary_groups=None, uid=None): |
161 | + """Add a user to the system. |
162 | |
163 | Will log but otherwise succeed if the user already exists. |
164 | |
165 | @@ -174,17 +185,23 @@ |
166 | :param str password: Password for user; if ``None``, create a system user |
167 | :param str shell: The default shell for the user |
168 | :param bool system_user: Whether to create a login or system user |
169 | - :param str primary_group: Primary group for user; defaults to their username |
170 | + :param str primary_group: Primary group for user; defaults to username |
171 | :param list secondary_groups: Optional list of additional groups |
172 | + :param int uid: UID for user being created |
173 | |
174 | :returns: The password database entry struct, as returned by `pwd.getpwnam` |
175 | """ |
176 | try: |
177 | user_info = pwd.getpwnam(username) |
178 | log('user {0} already exists!'.format(username)) |
179 | + if uid: |
180 | + user_info = pwd.getpwuid(int(uid)) |
181 | + log('user with uid {0} already exists!'.format(uid)) |
182 | except KeyError: |
183 | log('creating user {0}'.format(username)) |
184 | cmd = ['useradd'] |
185 | + if uid: |
186 | + cmd.extend(['--uid', str(uid)]) |
187 | if system_user or password is None: |
188 | cmd.append('--system') |
189 | else: |
190 | @@ -219,14 +236,58 @@ |
191 | return user_exists |
192 | |
193 | |
194 | -def add_group(group_name, system_group=False): |
195 | - """Add a group to the system""" |
196 | +def uid_exists(uid): |
197 | + """Check if a uid exists""" |
198 | + try: |
199 | + pwd.getpwuid(uid) |
200 | + uid_exists = True |
201 | + except KeyError: |
202 | + uid_exists = False |
203 | + return uid_exists |
204 | + |
205 | + |
206 | +def group_exists(groupname): |
207 | + """Check if a group exists""" |
208 | + try: |
209 | + grp.getgrnam(groupname) |
210 | + group_exists = True |
211 | + except KeyError: |
212 | + group_exists = False |
213 | + return group_exists |
214 | + |
215 | + |
216 | +def gid_exists(gid): |
217 | + """Check if a gid exists""" |
218 | + try: |
219 | + grp.getgrgid(gid) |
220 | + gid_exists = True |
221 | + except KeyError: |
222 | + gid_exists = False |
223 | + return gid_exists |
224 | + |
225 | + |
226 | +def add_group(group_name, system_group=False, gid=None): |
227 | + """Add a group to the system |
228 | + |
229 | + Will log but otherwise succeed if the group already exists. |
230 | + |
231 | + :param str group_name: group to create |
232 | + :param bool system_group: Create system group |
233 | + :param int gid: GID for user being created |
234 | + |
235 | + :returns: The password database entry struct, as returned by `grp.getgrnam` |
236 | + """ |
237 | try: |
238 | group_info = grp.getgrnam(group_name) |
239 | log('group {0} already exists!'.format(group_name)) |
240 | + if gid: |
241 | + group_info = grp.getgrgid(gid) |
242 | + log('group with gid {0} already exists!'.format(gid)) |
243 | except KeyError: |
244 | log('creating group {0}'.format(group_name)) |
245 | cmd = ['addgroup'] |
246 | + if gid: |
247 | + cmd.extend(['--gid', str(gid)]) |
248 | if system_group: |
249 | cmd.append('--system') |
250 | else: |
251 | @@ -300,14 +361,12 @@ |
252 | |
253 | |
254 | def fstab_remove(mp): |
255 | - """Remove the given mountpoint entry from /etc/fstab |
256 | - """ |
257 | + """Remove the given mountpoint entry from /etc/fstab""" |
258 | return Fstab.remove_by_mountpoint(mp) |
259 | |
260 | |
261 | def fstab_add(dev, mp, fs, options=None): |
262 | - """Adds the given device entry to the /etc/fstab file |
263 | - """ |
264 | + """Adds the given device entry to the /etc/fstab file""" |
265 | return Fstab.add(dev, mp, fs, options=options) |
266 | |
267 | |
268 | @@ -363,8 +422,7 @@ |
269 | |
270 | |
271 | def file_hash(path, hash_type='md5'): |
272 | - """ |
273 | - Generate a hash checksum of the contents of 'path' or None if not found. |
274 | + """Generate a hash checksum of the contents of 'path' or None if not found. |
275 | |
276 | :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`, |
277 | such as md5, sha1, sha256, sha512, etc. |
278 | @@ -379,10 +437,9 @@ |
279 | |
280 | |
281 | def path_hash(path): |
282 | - """ |
283 | - Generate a hash checksum of all files matching 'path'. Standard wildcards |
284 | - like '*' and '?' are supported, see documentation for the 'glob' module for |
285 | - more information. |
286 | + """Generate a hash checksum of all files matching 'path'. Standard |
287 | + wildcards like '*' and '?' are supported, see documentation for the 'glob' |
288 | + module for more information. |
289 | |
290 | :return: dict: A { filename: hash } dictionary for all matched files. |
291 | Empty if none found. |
292 | @@ -394,8 +451,7 @@ |
293 | |
294 | |
295 | def check_hash(path, checksum, hash_type='md5'): |
296 | - """ |
297 | - Validate a file using a cryptographic checksum. |
298 | + """Validate a file using a cryptographic checksum. |
299 | |
300 | :param str checksum: Value of the checksum used to validate the file. |
301 | :param str hash_type: Hash algorithm used to generate `checksum`. |
302 | @@ -410,10 +466,11 @@ |
303 | |
304 | |
305 | class ChecksumError(ValueError): |
306 | + """A class derived from Value error to indicate the checksum failed.""" |
307 | pass |
308 | |
309 | |
310 | -def restart_on_change(restart_map, stopstart=False): |
311 | +def restart_on_change(restart_map, stopstart=False, restart_functions=None): |
312 | """Restart services based on configuration files changing |
313 | |
314 | This function is used a decorator, for example:: |
315 | @@ -431,27 +488,58 @@ |
316 | restarted if any file matching the pattern got changed, created |
317 | or removed. Standard wildcards are supported, see documentation |
318 | for the 'glob' module for more information. |
319 | + |
320 | + @param restart_map: {path_file_name: [service_name, ...] |
321 | + @param stopstart: DEFAULT false; whether to stop, start OR restart |
322 | + @param restart_functions: nonstandard functions to use to restart services |
323 | + {svc: func, ...} |
324 | + @returns result from decorated function |
325 | """ |
326 | def wrap(f): |
327 | + @functools.wraps(f) |
328 | def wrapped_f(*args, **kwargs): |
329 | - checksums = {path: path_hash(path) for path in restart_map} |
330 | - f(*args, **kwargs) |
331 | - restarts = [] |
332 | - for path in restart_map: |
333 | - if path_hash(path) != checksums[path]: |
334 | - restarts += restart_map[path] |
335 | - services_list = list(OrderedDict.fromkeys(restarts)) |
336 | - if not stopstart: |
337 | - for service_name in services_list: |
338 | - service('restart', service_name) |
339 | - else: |
340 | - for action in ['stop', 'start']: |
341 | - for service_name in services_list: |
342 | - service(action, service_name) |
343 | + return restart_on_change_helper( |
344 | + (lambda: f(*args, **kwargs)), restart_map, stopstart, |
345 | + restart_functions) |
346 | return wrapped_f |
347 | return wrap |
348 | |
349 | |
350 | +def restart_on_change_helper(lambda_f, restart_map, stopstart=False, |
351 | + restart_functions=None): |
352 | + """Helper function to perform the restart_on_change function. |
353 | + |
354 | + This is provided for decorators to restart services if files described |
355 | + in the restart_map have changed after an invocation of lambda_f(). |
356 | + |
357 | + @param lambda_f: function to call. |
358 | + @param restart_map: {file: [service, ...]} |
359 | + @param stopstart: whether to stop, start or restart a service |
360 | + @param restart_functions: nonstandard functions to use to restart services |
361 | + {svc: func, ...} |
362 | + @returns result of lambda_f() |
363 | + """ |
364 | + if restart_functions is None: |
365 | + restart_functions = {} |
366 | + checksums = {path: path_hash(path) for path in restart_map} |
367 | + r = lambda_f() |
368 | + # create a list of lists of the services to restart |
369 | + restarts = [restart_map[path] |
370 | + for path in restart_map |
371 | + if path_hash(path) != checksums[path]] |
372 | + # create a flat list of ordered services without duplicates from lists |
373 | + services_list = list(OrderedDict.fromkeys(itertools.chain(*restarts))) |
374 | + if services_list: |
375 | + actions = ('stop', 'start') if stopstart else ('restart',) |
376 | + for service_name in services_list: |
377 | + if service_name in restart_functions: |
378 | + restart_functions[service_name](service_name) |
379 | + else: |
380 | + for action in actions: |
381 | + service(action, service_name) |
382 | + return r |
383 | + |
384 | + |
385 | def lsb_release(): |
386 | """Return /etc/lsb-release in a dict""" |
387 | d = {} |
388 | @@ -515,7 +603,7 @@ |
389 | |
390 | |
391 | def list_nics(nic_type=None): |
392 | - '''Return a list of nics of given type(s)''' |
393 | + """Return a list of nics of given type(s)""" |
394 | if isinstance(nic_type, six.string_types): |
395 | int_types = [nic_type] |
396 | else: |
397 | @@ -557,12 +645,13 @@ |
398 | |
399 | |
400 | def set_nic_mtu(nic, mtu): |
401 | - '''Set MTU on a network interface''' |
402 | + """Set the Maximum Transmission Unit (MTU) on a network interface.""" |
403 | cmd = ['ip', 'link', 'set', nic, 'mtu', mtu] |
404 | subprocess.check_call(cmd) |
405 | |
406 | |
407 | def get_nic_mtu(nic): |
408 | + """Return the Maximum Transmission Unit (MTU) for a network interface.""" |
409 | cmd = ['ip', 'addr', 'show', nic] |
410 | ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n') |
411 | mtu = "" |
412 | @@ -574,6 +663,7 @@ |
413 | |
414 | |
415 | def get_nic_hwaddr(nic): |
416 | + """Return the Media Access Control (MAC) for a network interface.""" |
417 | cmd = ['ip', '-o', '-0', 'addr', 'show', nic] |
418 | ip_output = subprocess.check_output(cmd).decode('UTF-8') |
419 | hwaddr = "" |
420 | @@ -584,7 +674,7 @@ |
421 | |
422 | |
423 | def cmp_pkgrevno(package, revno, pkgcache=None): |
424 | - '''Compare supplied revno with the revno of the installed package |
425 | + """Compare supplied revno with the revno of the installed package |
426 | |
427 | * 1 => Installed revno is greater than supplied arg |
428 | * 0 => Installed revno is the same as supplied arg |
429 | @@ -593,7 +683,7 @@ |
430 | This function imports apt_cache function from charmhelpers.fetch if |
431 | the pkgcache argument is None. Be sure to add charmhelpers.fetch if |
432 | you call this function, or pass an apt_pkg.Cache() instance. |
433 | - ''' |
434 | + """ |
435 | import apt_pkg |
436 | if not pkgcache: |
437 | from charmhelpers.fetch import apt_cache |
438 | @@ -603,19 +693,27 @@ |
439 | |
440 | |
441 | @contextmanager |
442 | -def chdir(d): |
443 | +def chdir(directory): |
444 | + """Change the current working directory to a different directory for a code |
445 | + block and return the previous directory after the block exits. Useful to |
446 | + run commands from a specificed directory. |
447 | + |
448 | + :param str directory: The directory path to change to for this context. |
449 | + """ |
450 | cur = os.getcwd() |
451 | try: |
452 | - yield os.chdir(d) |
453 | + yield os.chdir(directory) |
454 | finally: |
455 | os.chdir(cur) |
456 | |
457 | |
458 | def chownr(path, owner, group, follow_links=True, chowntopdir=False): |
459 | - """ |
460 | - Recursively change user and group ownership of files and directories |
461 | + """Recursively change user and group ownership of files and directories |
462 | in given path. Doesn't chown path itself by default, only its children. |
463 | |
464 | + :param str path: The string path to start changing ownership. |
465 | + :param str owner: The owner string to use when looking up the uid. |
466 | + :param str group: The group string to use when looking up the gid. |
467 | :param bool follow_links: Also Chown links if True |
468 | :param bool chowntopdir: Also chown path itself if True |
469 | """ |
470 | @@ -639,15 +737,23 @@ |
471 | |
472 | |
473 | def lchownr(path, owner, group): |
474 | + """Recursively change user and group ownership of files and directories |
475 | + in a given path, not following symbolic links. See the documentation for |
476 | + 'os.lchown' for more information. |
477 | + |
478 | + :param str path: The string path to start changing ownership. |
479 | + :param str owner: The owner string to use when looking up the uid. |
480 | + :param str group: The group string to use when looking up the gid. |
481 | + """ |
482 | chownr(path, owner, group, follow_links=False) |
483 | |
484 | |
485 | def get_total_ram(): |
486 | - '''The total amount of system RAM in bytes. |
487 | + """The total amount of system RAM in bytes. |
488 | |
489 | This is what is reported by the OS, and may be overcommitted when |
490 | there are multiple containers hosted on the same machine. |
491 | - ''' |
492 | + """ |
493 | with open('/proc/meminfo', 'r') as f: |
494 | for line in f.readlines(): |
495 | if line: |
496 | |
497 | === modified file 'charmhelpers/fetch/__init__.py' |
498 | --- charmhelpers/fetch/__init__.py 2016-01-11 18:16:28 +0000 |
499 | +++ charmhelpers/fetch/__init__.py 2016-06-10 10:55:43 +0000 |
500 | @@ -106,6 +106,14 @@ |
501 | 'mitaka/proposed': 'trusty-proposed/mitaka', |
502 | 'trusty-mitaka/proposed': 'trusty-proposed/mitaka', |
503 | 'trusty-proposed/mitaka': 'trusty-proposed/mitaka', |
504 | + # Newton |
505 | + 'newton': 'xenial-updates/newton', |
506 | + 'xenial-newton': 'xenial-updates/newton', |
507 | + 'xenial-newton/updates': 'xenial-updates/newton', |
508 | + 'xenial-updates/newton': 'xenial-updates/newton', |
509 | + 'newton/proposed': 'xenial-proposed/newton', |
510 | + 'xenial-newton/proposed': 'xenial-proposed/newton', |
511 | + 'xenial-proposed/newton': 'xenial-proposed/newton', |
512 | } |
513 | |
514 | # The order of this list is very important. Handlers should be listed in from |
515 | |
516 | === modified file 'charmhelpers/fetch/giturl.py' |
517 | --- charmhelpers/fetch/giturl.py 2016-01-11 18:16:28 +0000 |
518 | +++ charmhelpers/fetch/giturl.py 2016-06-10 10:55:43 +0000 |
519 | @@ -15,7 +15,7 @@ |
520 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
521 | |
522 | import os |
523 | -from subprocess import check_call |
524 | +from subprocess import check_call, CalledProcessError |
525 | from charmhelpers.fetch import ( |
526 | BaseFetchHandler, |
527 | UnhandledSource, |
528 | @@ -63,6 +63,8 @@ |
529 | branch_name) |
530 | try: |
531 | self.clone(source, dest_dir, branch, depth) |
532 | + except CalledProcessError as e: |
533 | + raise UnhandledSource(e) |
534 | except OSError as e: |
535 | raise UnhandledSource(e.strerror) |
536 | return dest_dir |
charm_lint_check #2850 mongodb for james-page mp297046
LINT OK: passed
Build: http:// 10.245. 162.36: 8080/job/ charm_lint_ check/2850/