Passed
Push — develop ( 9f128b...266903 )
by Plexxi
06:37 queued 03:15
created

get_trigger_db_by_id()   A

Complexity

Conditions 2

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
dl 0
loc 16
rs 9.4285
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 six
17
18
from st2common import log as logging
19
from st2common.constants.triggers import CRON_TIMER_TRIGGER_REF
20
from st2common.exceptions.sensors import TriggerTypeRegistrationException
21
from st2common.exceptions.triggers import TriggerDoesNotExistException
22
from st2common.exceptions.db import StackStormDBObjectNotFoundError
23
from st2common.models.api.trigger import (TriggerAPI, TriggerTypeAPI)
24
from st2common.models.system.common import ResourceReference
25
from st2common.persistence.trigger import (Trigger, TriggerType)
26
27
__all__ = [
28
    'add_trigger_models',
29
30
    'get_trigger_db_by_ref',
31
    'get_trigger_db_by_id',
32
    'get_trigger_db_by_uid',
33
    'get_trigger_db_given_type_and_params',
34
    'get_trigger_type_db',
35
36
    'create_trigger_db',
37
    'create_trigger_type_db',
38
39
    'create_or_update_trigger_db',
40
    'create_or_update_trigger_type_db'
41
]
42
43
LOG = logging.getLogger(__name__)
44
45
46
def get_trigger_db_given_type_and_params(type=None, parameters=None):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in type.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
47
    try:
48
        parameters = parameters or {}
49
        trigger_dbs = Trigger.query(type=type,
50
                                    parameters=parameters)
51
52
        trigger_db = trigger_dbs[0] if len(trigger_dbs) > 0 else None
53
54
        # NOTE: This is a work-around which we might be able to remove once we upgrade
55
        # pymongo and mongoengine
56
        # Work around for cron-timer when in some scenarios finding an object fails when Python
57
        # value types are unicode :/
58
        is_cron_trigger = (type == CRON_TIMER_TRIGGER_REF)
59
        has_parameters = bool(parameters)
60
61
        if not trigger_db and six.PY2 and is_cron_trigger and has_parameters:
62
            non_unicode_literal_parameters = {}
63
            for key, value in six.iteritems(parameters):
64
                key = key.encode('utf-8')
65
66
                if isinstance(value, six.text_type):
67
                    # We only encode unicode to str
68
                    value = value.encode('utf-8')
69
70
                non_unicode_literal_parameters[key] = value
71
            parameters = non_unicode_literal_parameters
72
73
            trigger_dbs = Trigger.query(type=type,
74
                                        parameters=non_unicode_literal_parameters).no_cache()
75
76
            # Note: We need to directly access the object, using len or accessing the query set
77
            # twice won't work - there seems to bug a bug with cursor where accessing it twice
78
            # will throw an exception
79
            try:
80
                trigger_db = trigger_dbs[0]
81
            except IndexError:
82
                trigger_db = None
83
84
        if not parameters and not trigger_db:
85
            # We need to do double query because some TriggeDB objects without
86
            # parameters have "parameters" attribute stored in the db and others
87
            # don't
88
            trigger_db = Trigger.query(type=type, parameters=None).first()
89
90
        return trigger_db
91
    except StackStormDBObjectNotFoundError as e:
92
        LOG.debug('Database lookup for type="%s" parameters="%s" resulted ' +
93
                  'in exception : %s.', type, parameters, e, exc_info=True)
94
        return None
95
96
97
def get_trigger_db_by_id(id):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
98
    """
99
    Returns the trigger object from db given a trigger id.
100
101
    :param ref: Reference to the trigger db object.
102
    :type ref: ``str``
103
104
    :rtype: ``object``
105
    """
106
    try:
107
        return Trigger.get_by_id(id)
108
    except StackStormDBObjectNotFoundError as e:
109
        LOG.debug('Database lookup for id="%s" resulted in exception : %s.',
110
                  id, e, exc_info=True)
111
112
    return None
113
114
115
def get_trigger_db_by_uid(uid):
116
    """
117
    Returns the trigger object from db given a trigger uid.
118
119
    :param ref: Reference to the trigger db object.
120
    :type ref: ``str``
121
122
    :rtype: ``object``
123
    """
124
    try:
125
        return Trigger.get_by_uid(uid)
126
    except StackStormDBObjectNotFoundError as e:
127
        LOG.debug('Database lookup for uid="%s" resulted in exception : %s.',
128
                  uid, e, exc_info=True)
129
130
    return None
131
132
133
def get_trigger_db_by_ref(ref):
134
    """
135
    Returns the trigger object from db given a string ref.
136
137
    :param ref: Reference to the trigger db object.
138
    :type ref: ``str``
139
140
    :rtype trigger_type: ``object``
141
    """
142
    try:
143
        return Trigger.get_by_ref(ref)
144
    except StackStormDBObjectNotFoundError as e:
145
        LOG.debug('Database lookup for ref="%s" resulted ' +
146
                  'in exception : %s.', ref, e, exc_info=True)
147
148
    return None
149
150
151
def _get_trigger_db(trigger):
152
    # TODO: This method should die in a fire
153
    # XXX: Do not make this method public.
154
155
    if isinstance(trigger, dict):
156
        name = trigger.get('name', None)
157
        pack = trigger.get('pack', None)
158
159
        if name and pack:
160
            ref = ResourceReference.to_string_reference(name=name, pack=pack)
161
            return get_trigger_db_by_ref(ref)
162
        return get_trigger_db_given_type_and_params(type=trigger['type'],
163
                                                    parameters=trigger.get('parameters', {}))
164
    else:
165
        raise Exception('Unrecognized object')
166
167
168
def get_trigger_type_db(ref):
169
    """
170
    Returns the trigger type object from db given a string ref.
171
172
    :param ref: Reference to the trigger type db object.
173
    :type ref: ``str``
174
175
    :rtype trigger_type: ``object``
176
    """
177
    try:
178
        return TriggerType.get_by_ref(ref)
179
    except StackStormDBObjectNotFoundError as e:
180
        LOG.debug('Database lookup for ref="%s" resulted ' +
181
                  'in exception : %s.', ref, e, exc_info=True)
182
183
    return None
184
185
186
def _get_trigger_dict_given_rule(rule):
187
    trigger = rule.trigger
188
    trigger_dict = {}
189
    triggertype_ref = ResourceReference.from_string_reference(trigger.get('type'))
190
    trigger_dict['pack'] = trigger_dict.get('pack', triggertype_ref.pack)
191
    trigger_dict['type'] = triggertype_ref.ref
192
    trigger_dict['parameters'] = rule.trigger.get('parameters', {})
193
194
    return trigger_dict
195
196
197
def create_trigger_db(trigger_api):
198
    # TODO: This is used only in trigger API controller. We should get rid of this.
199
    trigger_ref = ResourceReference.to_string_reference(name=trigger_api.name,
200
                                                        pack=trigger_api.pack)
201
    trigger_db = get_trigger_db_by_ref(trigger_ref)
202
    if not trigger_db:
203
        trigger_db = TriggerAPI.to_model(trigger_api)
204
        LOG.debug('Verified trigger and formulated TriggerDB=%s', trigger_db)
205
        trigger_db = Trigger.add_or_update(trigger_db)
206
    return trigger_db
207
208
209
def create_or_update_trigger_db(trigger):
210
    """
211
    Create a new TriggerDB model if one doesn't exist yet or update existing
212
    one.
213
214
    :param trigger: Trigger info.
215
    :type trigger: ``dict``
216
    """
217
    assert isinstance(trigger, dict)
218
219
    existing_trigger_db = _get_trigger_db(trigger)
220
221
    if existing_trigger_db:
222
        is_update = True
223
    else:
224
        is_update = False
225
226
    trigger_api = TriggerAPI(**trigger)
227
    trigger_api.validate()
228
    trigger_db = TriggerAPI.to_model(trigger_api)
229
230
    if is_update:
231
        trigger_db.id = existing_trigger_db.id
232
233
    trigger_db = Trigger.add_or_update(trigger_db)
234
235
    extra = {'trigger_db': trigger_db}
236
237
    if is_update:
238
        LOG.audit('Trigger updated. Trigger.id=%s' % (trigger_db.id), extra=extra)
239
    else:
240
        LOG.audit('Trigger created. Trigger.id=%s' % (trigger_db.id), extra=extra)
241
242
    return trigger_db
243
244
245
def create_trigger_db_from_rule(rule):
246
    trigger_dict = _get_trigger_dict_given_rule(rule)
247
    existing_trigger_db = _get_trigger_db(trigger_dict)
248
    # For simple triggertypes (triggertype with no parameters), we create a trigger when
249
    # registering triggertype. So if we hit the case that there is no trigger in db but
250
    # parameters is empty, then this case is a run time error.
251
    if not trigger_dict.get('parameters', {}) and not existing_trigger_db:
252
        raise TriggerDoesNotExistException(
253
            'A simple trigger should have been created when registering '
254
            'triggertype. Cannot create trigger: %s.' % (trigger_dict))
255
256
    if not existing_trigger_db:
257
        trigger_db = create_or_update_trigger_db(trigger_dict)
258
    else:
259
        trigger_db = existing_trigger_db
260
261
    # Special reference counting for trigger with parameters.
262
    # if trigger_dict.get('parameters', None):
263
    #     Trigger.update(trigger_db, inc__ref_count=1)
264
265
    return trigger_db
266
267
268
def increment_trigger_ref_count(rule_api):
269
    """
270
    Given the rule figures out the TriggerType with parameter and increments
271
    reference count on the appropriate Trigger.
272
273
    :param rule_api: Rule object used to infer the Trigger.
274
    :type rule_api: ``RuleApi``
275
    """
276
    trigger_dict = _get_trigger_dict_given_rule(rule_api)
277
278
    # Special reference counting for trigger with parameters.
279
    if trigger_dict.get('parameters', None):
280
        trigger_db = _get_trigger_db(trigger_dict)
281
        Trigger.update(trigger_db, inc__ref_count=1)
282
283
284
def cleanup_trigger_db_for_rule(rule_db):
285
    # rule.trigger is actually trigger_db ref.
286
    existing_trigger_db = get_trigger_db_by_ref(rule_db.trigger)
287
    if not existing_trigger_db or not existing_trigger_db.parameters:
288
        # nothing to be done here so moving on.
289
        LOG.debug('ref_count decrement for %s not required.', existing_trigger_db)
290
        return
291
    Trigger.update(existing_trigger_db, dec__ref_count=1)
292
    Trigger.delete_if_unreferenced(existing_trigger_db)
293
294
295
def create_trigger_type_db(trigger_type):
296
    """
297
    Creates a trigger type db object in the db given trigger_type definition as dict.
298
299
    :param trigger_type: Trigger type model.
300
    :type trigger_type: ``dict``
301
302
    :rtype: ``object``
303
    """
304
    trigger_type_api = TriggerTypeAPI(**trigger_type)
305
    trigger_type_api.validate()
306
    ref = ResourceReference.to_string_reference(name=trigger_type_api.name,
307
                                                pack=trigger_type_api.pack)
308
    trigger_type_db = get_trigger_type_db(ref)
309
310
    if not trigger_type_db:
311
        trigger_type_db = TriggerTypeAPI.to_model(trigger_type_api)
312
        LOG.debug('verified trigger and formulated TriggerDB=%s', trigger_type_db)
313
        trigger_type_db = TriggerType.add_or_update(trigger_type_db)
314
    return trigger_type_db
315
316
317
def create_shadow_trigger(trigger_type_db):
318
    """
319
    Create a shadow trigger for TriggerType with no parameters.
320
    """
321
    trigger_type_ref = trigger_type_db.get_reference().ref
322
323
    if trigger_type_db.parameters_schema:
324
        LOG.debug('Skip shadow trigger for TriggerType with parameters %s.', trigger_type_ref)
325
        return None
326
327
    trigger = {'name': trigger_type_db.name,
328
               'pack': trigger_type_db.pack,
329
               'type': trigger_type_ref,
330
               'parameters': {}}
331
332
    return create_or_update_trigger_db(trigger)
333
334
335
def create_or_update_trigger_type_db(trigger_type):
336
    """
337
    Create or update a trigger type db object in the db given trigger_type definition as dict.
338
339
    :param trigger_type: Trigger type model.
340
    :type trigger_type: ``dict``
341
342
    :rtype: ``object``
343
    """
344
    assert isinstance(trigger_type, dict)
345
346
    trigger_type_api = TriggerTypeAPI(**trigger_type)
347
    trigger_type_api.validate()
348
    trigger_type_api = TriggerTypeAPI.to_model(trigger_type_api)
349
350
    ref = ResourceReference.to_string_reference(name=trigger_type_api.name,
351
                                                pack=trigger_type_api.pack)
352
353
    existing_trigger_type_db = get_trigger_type_db(ref)
354
    if existing_trigger_type_db:
355
        is_update = True
356
    else:
357
        is_update = False
358
359
    if is_update:
360
        trigger_type_api.id = existing_trigger_type_db.id
361
362
    trigger_type_db = TriggerType.add_or_update(trigger_type_api)
363
364
    extra = {'trigger_type_db': trigger_type_db}
365
366
    if is_update:
367
        LOG.audit('TriggerType updated. TriggerType.id=%s' % (trigger_type_db.id), extra=extra)
368
    else:
369
        LOG.audit('TriggerType created. TriggerType.id=%s' % (trigger_type_db.id), extra=extra)
370
371
    return trigger_type_db
372
373
374
def _create_trigger_type(pack, name, description=None, payload_schema=None,
375
                         parameters_schema=None, tags=None):
376
    trigger_type = {
377
        'name': name,
378
        'pack': pack,
379
        'description': description,
380
        'payload_schema': payload_schema,
381
        'parameters_schema': parameters_schema,
382
        'tags': tags
383
    }
384
385
    return create_or_update_trigger_type_db(trigger_type=trigger_type)
386
387
388
def _validate_trigger_type(trigger_type):
389
    """
390
    XXX: We need validator objects that define the required and optional fields.
391
    For now, manually check them.
392
    """
393
    required_fields = ['name']
394
    for field in required_fields:
395
        if field not in trigger_type:
396
            raise TriggerTypeRegistrationException('Invalid trigger type. Missing field "%s"' %
397
                                                   (field))
398
399
400
def _create_trigger(trigger_type):
401
    """
402
    :param trigger_type: TriggerType db object.
403
    :type trigger_type: :class:`TriggerTypeDB`
404
    """
405
    if hasattr(trigger_type, 'parameters_schema') and not trigger_type['parameters_schema']:
406
        trigger_dict = {
407
            'name': trigger_type.name,
408
            'pack': trigger_type.pack,
409
            'type': trigger_type.get_reference().ref
410
        }
411
412
        try:
413
            return create_or_update_trigger_db(trigger=trigger_dict)
414
        except:
415
            LOG.exception('Validation failed for Trigger=%s.', trigger_dict)
416
            raise TriggerTypeRegistrationException(
417
                'Unable to create Trigger for TriggerType=%s.' % trigger_type.name)
418
    else:
419
        LOG.debug('Won\'t create Trigger object as TriggerType %s expects ' +
420
                  'parameters.', trigger_type)
421
        return None
422
423
424
def _add_trigger_models(trigger_type):
425
    pack = trigger_type['pack']
426
    description = trigger_type['description'] if 'description' in trigger_type else ''
427
    payload_schema = trigger_type['payload_schema'] if 'payload_schema' in trigger_type else {}
428
    parameters_schema = trigger_type['parameters_schema'] \
429
        if 'parameters_schema' in trigger_type else {}
430
    tags = trigger_type.get('tags', [])
431
432
    trigger_type = _create_trigger_type(
433
        pack=pack,
434
        name=trigger_type['name'],
435
        description=description,
436
        payload_schema=payload_schema,
437
        parameters_schema=parameters_schema,
438
        tags=tags
439
    )
440
    trigger = _create_trigger(trigger_type=trigger_type)
441
    return (trigger_type, trigger)
442
443
444
def add_trigger_models(trigger_types):
445
    """
446
    Register trigger types.
447
448
    :param trigger_types: A list of triggers to register.
449
    :type trigger_types: ``list`` of ``dict``
450
451
    :rtype: ``list`` of ``tuple`` (trigger_type, trigger)
452
    """
453
    [r for r in (_validate_trigger_type(trigger_type)
454
     for trigger_type in trigger_types) if r is not None]
455
456
    result = []
457
    for trigger_type in trigger_types:
458
        item = _add_trigger_models(trigger_type=trigger_type)
459
460
        if item:
461
            result.append(item)
462
463
    return result
464