Test Failed
Push — develop ( 66c9ff...e21229 )
by Nicolas
05:06
created

glances/plugins/glances_smart.py (15 issues)

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2018 Tim Nibert <[email protected]>
6
#
7
# Glances is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU Lesser General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Glances is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
"""
21
Hard disk SMART attributes plugin.
22
Depends on pySMART and smartmontools
23
Must execute as root
24
"usermod -a -G disk USERNAME" is not sufficient unfortunately
25
SmartCTL (/usr/sbin/smartctl) must be in system path for python2.
26
27
Regular PySMART is a python2 library.
28
We are using the pySMART.smartx updated library to support both python 2 and 3.
29
30
If we only have disk group access (no root):
31
$ smartctl -i /dev/sda
32
smartctl 6.6 2016-05-31 r4324 [x86_64-linux-4.15.0-30-generic] (local build)
33
Copyright (C) 2002-16, Bruce Allen, Christian Franke, www.smartmontools.org
34
35
36
Probable ATA device behind a SAT layer
37
Try an additional '-d ata' or '-d sat' argument.
38
39
This is not very hopeful: https://medium.com/opsops/why-smartctl-could-not-be-run-without-root-7ea0583b1323
0 ignored issues
show
This line is too long as per the coding-style (107/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
40
41
So, here is what we are going to do:
42
Check for admin access.  If no admin access, disable SMART plugin.
43
44
If smartmontools is not installed, we should catch the error upstream in plugin initialization.
0 ignored issues
show
This line is too long as per the coding-style (95/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
45
"""
46
47
from glances.plugins.glances_plugin import GlancesPlugin
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
48
from glances.logger import logger
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
49
from glances.main import disable
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
50
import os
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
standard import "import os" should be placed before "from glances.plugins.glances_plugin import GlancesPlugin"
Loading history...
51
52
# Import plugin specific dependency
53
try:
54
    from pySMART import DeviceList
55
except ImportError as e:
56
    import_error_tag = True
57
    logger.warning("Missing Python Lib ({}), HDD Smart plugin is disabled".format(e))
58
else:
59
    import_error_tag = False
60
61
DEVKEY = "DeviceName"
62
63
64
def is_admin():
65
    """
66
    https://stackoverflow.com/a/19719292
67
    @return: True if the current user is an 'Admin' whatever that
68
    means (root on Unix), otherwise False.
69
    Warning: The inner function fails unless you have Windows XP SP2 or
70
    higher. The failure causes a traceback to be printed and this
71
    function to return False.
72
    """
73
74
    if os.name == 'nt':
75
        import ctypes
76
        import traceback
77
        # WARNING: requires Windows XP SP2 or higher!
78
        try:
79
            return ctypes.windll.shell32.IsUserAnAdmin()
80
        except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
81
            traceback.print_exc()
82
            return False
83
    else:
84
        # Check for root on Posix
85
        return os.getuid() == 0
86
87
88
def convert_attribute_to_dict(attr):
0 ignored issues
show
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
89
    return {
90
        'num': attr.num,
91
        'flags': attr.flags,
92
        'raw': attr.raw,
93
        'value': attr.value,
94
        'worst': attr.worst,
95
        'threshold': attr.thresh,
96
        'type': attr.type,
97
        'updated': attr.updated,
98
        'when_failed': attr.when_failed,
99
    }
100
101
102
def get_smart_data():
103
    """
104
    Get SMART attribute data
105
    :return: list of multi leveled dictionaries
106
             each dict has a key "DeviceName" with the identification of the device in smartctl
0 ignored issues
show
This line is too long as per the coding-style (95/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
107
             also has keys of the SMART attribute id, with value of another dict of the attributes
0 ignored issues
show
This line is too long as per the coding-style (98/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
108
             [
109
                {
110
                    "DeviceName": "/dev/sda blahblah",
111
                    "1":
112
                    {
113
                        "flags": "..",
114
                        "raw": "..",
115
                        etc,
116
                    }
117
                }
118
             ]
119
    """
120
    stats = []
121
    # get all devices
122
    devlist = DeviceList()
123
124
    for dev in devlist.devices:
125
        stats.append({
126
            DEVKEY: str(dev)
127
        })
128
        for attribute in dev.attributes:
129
            if attribute is None:
130
                pass
131
            else:
132
                attribdict = convert_attribute_to_dict(attribute)
133
134
                # we will use the attribute number as the key
135
                num = attribdict.pop('num', None)
136
                try:
137
                    assert num is not None
138
                except Exception as e:
0 ignored issues
show
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
Coding Style Naming introduced by
The name e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
The variable e seems to be unused.
Loading history...
139
                    # we should never get here, but if we do, continue to next iteration and skip this attribute
0 ignored issues
show
This line is too long as per the coding-style (112/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
140
                    continue
141
142
                stats[-1][num] = attribdict
143
    return stats
144
145
146
class Plugin(GlancesPlugin):
147
    """
148
    Glances' HDD SMART plugin.
149
150
    stats is a list of dicts
151
    """
152
153
    def __init__(self,
154
                 args=None,
155
                 config=None,
156
                 stats_init_value=[]):
157
        """Init the plugin."""
158
        # check if user is admin
159
        if not is_admin():
160
            disable(args, "smart")
161
            logger.debug("Current user is not admin, HDD SMART plugin disabled.")
162
163
        super(Plugin, self).__init__(args=args, config=config)
164
165
        # We want to display the stat in the curse interface
166
        self.display_curse = True
167
168
    @GlancesPlugin._check_decorator
169
    @GlancesPlugin._log_result_decorator
170
    def update(self):
171
        """Update SMART stats using the input method."""
172
        # Init new stats
173
        stats = self.get_init_value()
174
175
        if import_error_tag:
176
            return self.stats
177
178
        if self.input_method == 'local':
179
            stats = get_smart_data()
180
        elif self.input_method == 'snmp':
181
            pass
182
183
        # Update the stats
184
        self.stats = stats
185
186
        return self.stats
187
188
    def get_key(self):
189
        """Return the key of the list."""
190
        return DEVKEY
191