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

glances/plugins/glances_cloud.py (1 issue)

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
27
28
from glances.compat import iteritems, to_ascii
29
from glances.plugins.glances_plugin import GlancesPlugin
30
from glances.logger import logger
31
32
# Import plugin specific dependency
33
try:
34
    import requests
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))
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)
0 ignored issues
show
Coding Style Naming introduced by
The name r 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...
166
            except Exception as e:
167
                logger.debug('cloud plugin - Cannot connect to the OpenStack metadata API {}: {}'.format(r_url, e))
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):
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