Completed
Push — master ( dfd3a9...2b76ca )
by Manas
05:54
created

st2reactor.container.SensorService.get_value()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 2
rs 10
cc 1
1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
import os
17
import sys
18
import json
19
import atexit
20
import argparse
21
22
import eventlet
23
from oslo_config import cfg
24
25
from st2common import log as logging
26
from st2common.logging.misc import set_log_level_for_all_loggers
27
from st2common.models.api.trace import TraceContext
28
from st2common.persistence.db_init import db_setup_with_retry
29
from st2common.transport.reactor import TriggerDispatcher
30
from st2common.util import loader
31
from st2common.util.config_parser import ContentPackConfigParser
32
from st2common.services.triggerwatcher import TriggerWatcher
33
from st2reactor.sensor.base import Sensor, PollingSensor
34
from st2reactor.sensor import config
35
from st2common.services.datastore import DatastoreService
36
37
__all__ = [
38
    'SensorWrapper'
39
]
40
41
eventlet.monkey_patch(
42
    os=True,
43
    select=True,
44
    socket=True,
45
    thread=False if '--use-debugger' in sys.argv else True,
46
    time=True)
47
48
49
class SensorService(object):
50
    """
51
    Instance of this class is passed to the sensor instance and exposes "public"
52
    methods which can be called by the sensor.
53
    """
54
55
    def __init__(self, sensor_wrapper):
56
        self._sensor_wrapper = sensor_wrapper
57
        self._logger = self._sensor_wrapper._logger
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _logger was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
58
        self._dispatcher = TriggerDispatcher(self._logger)
59
        self._datastore_service = DatastoreService(logger=self._logger,
60
                                                   pack_name=self._sensor_wrapper._pack,
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _pack was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
61
                                                   class_name=self._sensor_wrapper._class_name,
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _class_name was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
62
                                                   api_username='sensor_service')
63
64
        self._client = None
65
66
    def get_logger(self, name):
67
        """
68
        Retrieve an instance of a logger to be used by the sensor class.
69
        """
70
        logger_name = '%s.%s' % (self._sensor_wrapper._logger.name, name)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _logger was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
71
        logger = logging.getLogger(logger_name)
72
        logger.propagate = True
73
74
        return logger
75
76
    def dispatch(self, trigger, payload=None, trace_tag=None):
77
        """
78
        Method which dispatches the trigger.
79
80
        :param trigger: Full name / reference of the trigger.
81
        :type trigger: ``str``
82
83
        :param payload: Trigger payload.
84
        :type payload: ``dict``
85
86
        :param trace_tag: Tracer to track the triggerinstance.
87
        :type trace_tags: ``str``
88
        """
89
        # empty strings
90
        trace_context = TraceContext(trace_tag=trace_tag) if trace_tag else None
91
        self.dispatch_with_context(trigger, payload=payload, trace_context=trace_context)
92
93
    def dispatch_with_context(self, trigger, payload=None, trace_context=None):
94
        """
95
        Method which dispatches the trigger.
96
97
        :param trigger: Full name / reference of the trigger.
98
        :type trigger: ``str``
99
100
        :param payload: Trigger payload.
101
        :type payload: ``dict``
102
103
        :param trace_context: Trace context to associate with Trigger.
104
        :type trace_context: ``st2common.api.models.api.trace.TraceContext``
105
        """
106
        self._dispatcher.dispatch(trigger, payload=payload, trace_context=trace_context)
107
108
    ##################################
109
    # Methods for datastore management
110
    ##################################
111
112
    def list_values(self, local=True, prefix=None):
113
        return self._datastore_service.list_values(local, prefix)
114
115
    def get_value(self, name, local=True):
116
        return self._datastore_service.get_value(name, local)
117
118
    def set_value(self, name, value, ttl=None, local=True):
119
        return self._datastore_service.set_value(name, value, ttl, local)
120
121
    def delete_value(self, name, local=True):
122
        return self._datastore_service.delete_value(name, local)
123
124
125
class SensorWrapper(object):
126
    def __init__(self, pack, file_path, class_name, trigger_types,
0 ignored issues
show
Comprehensibility Bug introduced by
trigger_types is re-defining a name which is already available in the outer-scope (previously defined on line 322).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
127
                 poll_interval=None, parent_args=None):
0 ignored issues
show
Comprehensibility Bug introduced by
parent_args is re-defining a name which is already available in the outer-scope (previously defined on line 324).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
128
        """
129
        :param pack: Name of the pack this sensor belongs to.
130
        :type pack: ``str``
131
132
        :param file_path: Path to the sensor module file.
133
        :type file_path: ``str``
134
135
        :param class_name: Sensor class name.
136
        :type class_name: ``str``
137
138
        :param trigger_types: A list of references to trigger types which
139
                                  belong to this sensor.
140
        :type trigger_types: ``list`` of ``str``
141
142
        :param poll_interval: Sensor poll interval (in seconds).
143
        :type poll_interval: ``int`` or ``None``
144
145
        :param parent_args: Command line arguments passed to the parent process.
146
        :type parse_args: ``list``
147
        """
148
        self._pack = pack
149
        self._file_path = file_path
150
        self._class_name = class_name
151
        self._trigger_types = trigger_types or []
152
        self._poll_interval = poll_interval
153
        self._parent_args = parent_args or []
154
        self._trigger_names = {}
155
156
        # 1. Parse the config with inherited parent args
157
        try:
158
            config.parse_args(args=self._parent_args)
159
        except Exception:
160
            pass
161
162
        # 2. Establish DB connection
163
        username = cfg.CONF.database.username if hasattr(cfg.CONF.database, 'username') else None
164
        password = cfg.CONF.database.password if hasattr(cfg.CONF.database, 'password') else None
165
        db_setup_with_retry(cfg.CONF.database.db_name, cfg.CONF.database.host,
166
                            cfg.CONF.database.port, username=username, password=password)
167
168
        # 3. Instantiate the watcher
169
        self._trigger_watcher = TriggerWatcher(create_handler=self._handle_create_trigger,
170
                                               update_handler=self._handle_update_trigger,
171
                                               delete_handler=self._handle_delete_trigger,
172
                                               trigger_types=self._trigger_types,
173
                                               queue_suffix='sensorwrapper_%s_%s' %
174
                                               (self._pack, self._class_name),
175
                                               exclusive=True)
176
177
        # 4. Set up logging
178
        self._logger = logging.getLogger('SensorWrapper.%s' %
179
                                         (self._class_name))
180
        logging.setup(cfg.CONF.sensorcontainer.logging)
181
182
        if '--debug' in parent_args:
183
            set_log_level_for_all_loggers()
184
185
        self._sensor_instance = self._get_sensor_instance()
186
187
    def run(self):
188
        atexit.register(self.stop)
189
190
        self._trigger_watcher.start()
191
        self._logger.info('Watcher started')
192
193
        self._logger.info('Running sensor initialization code')
194
        self._sensor_instance.setup()
195
196
        if self._poll_interval:
197
            message = ('Running sensor in active mode (poll interval=%ss)' %
198
                       (self._poll_interval))
199
        else:
200
            message = 'Running sensor in passive mode'
201
202
        self._logger.info(message)
203
204
        try:
205
            self._sensor_instance.run()
206
        except Exception as e:
207
            # Include traceback
208
            msg = ('Sensor "%s" run method raised an exception: %s.' %
209
                   (self._class_name, str(e)))
210
            self._logger.warn(msg, exc_info=True)
211
            raise Exception(msg)
212
213
    def stop(self):
214
        # Stop watcher
215
        self._logger.info('Stopping trigger watcher')
216
        self._trigger_watcher.stop()
217
218
        # Run sensor cleanup code
219
        self._logger.info('Invoking cleanup on sensor')
220
        self._sensor_instance.cleanup()
221
222
    ##############################################
223
    # Event handler methods for the trigger events
224
    ##############################################
225
226
    def _handle_create_trigger(self, trigger):
227
        self._logger.debug('Calling sensor "add_trigger" method (trigger.type=%s)' %
228
                           (trigger.type))
229
        self._trigger_names[str(trigger.id)] = trigger
230
231
        trigger = self._sanitize_trigger(trigger=trigger)
232
        self._sensor_instance.add_trigger(trigger=trigger)
233
234
    def _handle_update_trigger(self, trigger):
235
        self._logger.debug('Calling sensor "update_trigger" method (trigger.type=%s)' %
236
                           (trigger.type))
237
        self._trigger_names[str(trigger.id)] = trigger
238
239
        trigger = self._sanitize_trigger(trigger=trigger)
240
        self._sensor_instance.update_trigger(trigger=trigger)
241
242
    def _handle_delete_trigger(self, trigger):
243
        trigger_id = str(trigger.id)
244
        if trigger_id not in self._trigger_names:
245
            return
246
247
        self._logger.debug('Calling sensor "remove_trigger" method (trigger.type=%s)' %
248
                           (trigger.type))
249
        del self._trigger_names[trigger_id]
250
251
        trigger = self._sanitize_trigger(trigger=trigger)
252
        self._sensor_instance.remove_trigger(trigger=trigger)
253
254
    def _get_sensor_instance(self):
255
        """
256
        Retrieve instance of a sensor class.
257
        """
258
        _, filename = os.path.split(self._file_path)
259
        module_name, _ = os.path.splitext(filename)
260
261
        sensor_class = loader.register_plugin_class(base_class=Sensor,
262
                                                    file_path=self._file_path,
263
                                                    class_name=self._class_name)
264
265
        if not sensor_class:
266
            raise ValueError('Sensor module is missing a class with name "%s"' %
267
                             (self._class_name))
268
269
        sensor_class_kwargs = {}
270
        sensor_class_kwargs['sensor_service'] = SensorService(sensor_wrapper=self)
271
272
        sensor_config = self._get_sensor_config()
273
        sensor_class_kwargs['config'] = sensor_config
274
275
        if self._poll_interval and issubclass(sensor_class, PollingSensor):
276
            sensor_class_kwargs['poll_interval'] = self._poll_interval
277
278
        try:
279
            sensor_instance = sensor_class(**sensor_class_kwargs)
280
        except Exception:
281
            self._logger.exception('Failed to instantiate "%s" sensor class' % (self._class_name))
282
            raise Exception('Failed to instantiate "%s" sensor class' % (self._class_name))
283
284
        return sensor_instance
285
286
    def _get_sensor_config(self):
287
        config_parser = ContentPackConfigParser(pack_name=self._pack)
288
        config = config_parser.get_sensor_config(sensor_file_path=self._file_path)
0 ignored issues
show
Comprehensibility Bug introduced by
config is re-defining a name which is already available in the outer-scope (previously defined on line 34).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
289
290
        if config:
291
            self._logger.info('Using config "%s" for sensor "%s"' % (config.file_path,
292
                                                                     self._class_name))
293
            return config.config
294
        else:
295
            self._logger.info('No config found for sensor "%s"' % (self._class_name))
296
            return {}
297
298
    def _sanitize_trigger(self, trigger):
299
        sanitized = trigger._data
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _data was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
300
        if 'id' in sanitized:
301
            # Friendly objectid rather than the MongoEngine representation.
302
            sanitized['id'] = str(sanitized['id'])
303
        return sanitized
304
305
306
if __name__ == '__main__':
307
    parser = argparse.ArgumentParser(description='Sensor runner wrapper')
308
    parser.add_argument('--pack', required=True,
309
                        help='Name of the pack this sensor belongs to')
310
    parser.add_argument('--file-path', required=True,
311
                        help='Path to the sensor module')
312
    parser.add_argument('--class-name', required=True,
313
                        help='Name of the sensor class')
314
    parser.add_argument('--trigger-type-refs', required=False,
315
                        help='Comma delimited string of trigger type references')
316
    parser.add_argument('--poll-interval', type=int, default=None, required=False,
317
                        help='Sensor poll interval')
318
    parser.add_argument('--parent-args', required=False,
319
                        help='Command line arguments passed to the parent process')
320
    args = parser.parse_args()
321
322
    trigger_types = args.trigger_type_refs
323
    trigger_types = trigger_types.split(',') if trigger_types else []
324
    parent_args = json.loads(args.parent_args) if args.parent_args else []
325
    assert isinstance(parent_args, list)
326
327
    obj = SensorWrapper(pack=args.pack,
328
                        file_path=args.file_path,
329
                        class_name=args.class_name,
330
                        trigger_types=trigger_types,
331
                        poll_interval=args.poll_interval,
332
                        parent_args=parent_args)
333
    obj.run()
334