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

glances/plugins/glances_cloud.py (9 issues)

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2019 Nicolargo <[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
"""Cloud plugin.
21
22
Supported Cloud API:
23
- OpenStack meta data (class ThreadOpenStack, see bellow): AWS, OVH...
24
"""
25
26
import threading
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
27
28
from glances.compat import iteritems, to_ascii
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
29
from glances.plugins.glances_plugin import GlancesPlugin
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
30
from glances.logger import logger
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
31
32
# Import plugin specific dependency
33
try:
34
    import requests
0 ignored issues
show
import missing from __future__ import absolute_import
Loading history...
35
except ImportError as e:
36
    import_error_tag = True
37
    # Display debu message if import KeyError
38
    logger.warning("Missing Python Lib ({}), Cloud plugin is disabled".format(e))
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
39
else:
40
    import_error_tag = False
41
42
43
class Plugin(GlancesPlugin):
44
    """Glances' cloud plugin.
45
46
    The goal of this plugin is to retreive additional information
47
    concerning the datacenter where the host is connected.
48
49
    See https://github.com/nicolargo/glances/issues/1029
50
51
    stats is a dict
52
    """
53
54
    def __init__(self, args=None, config=None):
55
        """Init the plugin."""
56
        super(Plugin, self).__init__(args=args, config=config)
57
58
        # We want to display the stat in the curse interface
59
        self.display_curse = True
60
61
        # Init the stats
62
        self.reset()
63
64
        # Init thread to grab OpenStack stats asynchroniously
65
        self.OPENSTACK = ThreadOpenStack()
66
67
        # Run the thread
68
        self.OPENSTACK.start()
69
70
    def exit(self):
71
        """Overwrite the exit method to close threads."""
72
        self.OPENSTACK.stop()
73
        # Call the father class
74
        super(Plugin, self).exit()
75
76
    @GlancesPlugin._check_decorator
77
    @GlancesPlugin._log_result_decorator
78
    def update(self):
79
        """Update the cloud stats.
80
81
        Return the stats (dict)
82
        """
83
        # Init new stats
84
        stats = self.get_init_value()
85
86
        # Requests lib is needed to get stats from the Cloud API
87
        if import_error_tag:
88
            return stats
89
90
        # Update the stats
91
        if self.input_method == 'local':
92
            stats = self.OPENSTACK.stats
93
            # Example:
94
            # Uncomment to test on physical computer
95
            # stats = {'ami-id': 'ami-id',
96
            #                    'instance-id': 'instance-id',
97
            #                    'instance-type': 'instance-type',
98
            #                    'region': 'placement/availability-zone'}
99
100
        # Update the stats
101
        self.stats = stats
102
103
        return self.stats
104
105
    def msg_curse(self, args=None, max_width=None):
106
        """Return the string to display in the curse interface."""
107
        # Init the return message
108
        ret = []
109
110
        if not self.stats or self.stats == {} or self.is_disable():
111
            return ret
112
113
        # Generate the output
114
        if 'instance-type' in self.stats \
115
           and 'instance-id' in self.stats \
116
           and 'region' in self.stats:
117
            msg = 'Cloud '
118
            ret.append(self.curse_add_line(msg, "TITLE"))
119
            msg = '{} instance {} ({})'.format(self.stats['instance-type'],
120
                                               self.stats['instance-id'],
121
                                               self.stats['region'])
122
            ret.append(self.curse_add_line(msg))
123
124
        # Return the message with decoration
125
        # logger.info(ret)
126
        return ret
127
128
129
class ThreadOpenStack(threading.Thread):
130
    """
131
    Specific thread to grab OpenStack stats.
132
133
    stats is a dict
134
    """
135
136
    # https://docs.openstack.org/nova/latest/user/metadata-service.html
137
    OPENSTACK_API_URL = 'http://169.254.169.254/latest/meta-data'
138
    OPENSTACK_API_METADATA = {'ami-id': 'ami-id',
139
                              'instance-id': 'instance-id',
140
                              'instance-type': 'instance-type',
141
                              'region': 'placement/availability-zone'}
142
143
    def __init__(self):
144
        """Init the class."""
145
        logger.debug("cloud plugin - Create thread for OpenStack metadata")
146
        super(ThreadOpenStack, self).__init__()
147
        # Event needed to stop properly the thread
148
        self._stopper = threading.Event()
149
        # The class return the stats as a dict
150
        self._stats = {}
151
152
    def run(self):
153
        """Grab plugin's stats.
154
155
        Infinite loop, should be stopped by calling the stop() method
156
        """
157
        if import_error_tag:
158
            self.stop()
159
            return False
160
161
        for k, v in iteritems(self.OPENSTACK_API_METADATA):
162
            r_url = '{}/{}'.format(self.OPENSTACK_API_URL, v)
163
            try:
164
                # Local request, a timeout of 3 seconds is OK
165
                r = requests.get(r_url, timeout=3)
166
            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...
167
                logger.debug('cloud plugin - Cannot connect to the OpenStack metadata API {}: {}'.format(r_url, e))
0 ignored issues
show
Use formatting in logging functions and pass the parameters as arguments
Loading history...
168
                break
169
            else:
170
                if r.ok:
171
                    self._stats[k] = to_ascii(r.content)
172
173
        return True
174
175
    @property
176
    def stats(self):
177
        """Stats getter."""
178
        return self._stats
179
180
    @stats.setter
181
    def stats(self, value):
182
        """Stats setter."""
183
        self._stats = value
184
185
    def stop(self, timeout=None):
0 ignored issues
show
The argument timeout seems to be unused.
Loading history...
186
        """Stop the thread."""
187
        logger.debug("cloud plugin - Close thread for OpenStack metadata")
188
        self._stopper.set()
189
190
    def stopped(self):
191
        """Return True is the thread is stopped."""
192
        return self._stopper.isSet()
193