Test Failed
Pull Request — master (#4197)
by W
03:53
created

FixturesLoader   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 236
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 236
rs 9.52
c 0
b 0
f 0
wmc 36

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __init__() 0 2 1
A get_fixture_file_path_abs() 0 2 1
A _is_fixture_pack_exists() 0 2 1
A _get_fixture_file_path_abs() 0 2 1
A _get_fixtures_pack_path() 0 2 1
B delete_models_from_db() 0 25 5
B load_models() 0 41 4
A _validate_fixture_dict() 0 5 3
A load_fixtures() 0 34 4
C save_fixtures_to_db() 0 60 7
A _validate_fixtures_pack() 0 7 2
B delete_fixtures_from_db() 0 42 6
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
from __future__ import absolute_import
17
import copy
18
import os
19
20
import six
21
22
from st2common.content.loader import MetaLoader
23
24
from st2common.models.api.action import (ActionAPI, LiveActionAPI, ActionExecutionStateAPI,
25
                                         RunnerTypeAPI, ActionAliasAPI)
26
from st2common.models.api.auth import ApiKeyAPI, UserAPI
27
from st2common.models.api.execution import (ActionExecutionAPI)
28
from st2common.models.api.policy import (PolicyTypeAPI, PolicyAPI)
29
from st2common.models.api.rule import (RuleAPI)
30
from st2common.models.api.rule_enforcement import RuleEnforcementAPI
31
from st2common.models.api.sensor import SensorTypeAPI
32
from st2common.models.api.trace import TraceAPI
33
from st2common.models.api.trigger import (TriggerAPI, TriggerTypeAPI, TriggerInstanceAPI)
34
35
from st2common.models.db.action import ActionDB
36
from st2common.models.db.actionalias import ActionAliasDB
37
from st2common.models.db.auth import ApiKeyDB, UserDB
38
from st2common.models.db.liveaction import LiveActionDB
39
from st2common.models.db.executionstate import ActionExecutionStateDB
40
from st2common.models.db.runner import RunnerTypeDB
41
from st2common.models.db.execution import (ActionExecutionDB)
42
from st2common.models.db.policy import (PolicyTypeDB, PolicyDB)
43
from st2common.models.db.rule import RuleDB
44
from st2common.models.db.rule_enforcement import RuleEnforcementDB
45
from st2common.models.db.sensor import SensorTypeDB
46
from st2common.models.db.trace import TraceDB
47
from st2common.models.db.trigger import (TriggerDB, TriggerTypeDB, TriggerInstanceDB)
48
from st2common.persistence.action import Action
49
from st2common.persistence.actionalias import ActionAlias
50
from st2common.persistence.execution import ActionExecution
51
from st2common.persistence.executionstate import ActionExecutionState
52
from st2common.persistence.auth import ApiKey, User
53
from st2common.persistence.liveaction import LiveAction
54
from st2common.persistence.runner import RunnerType
55
from st2common.persistence.policy import (PolicyType, Policy)
56
from st2common.persistence.rule import Rule
57
from st2common.persistence.rule_enforcement import RuleEnforcement
58
from st2common.persistence.sensor import SensorType
59
from st2common.persistence.trace import Trace
60
from st2common.persistence.trigger import (Trigger, TriggerType, TriggerInstance)
61
62
63
ALLOWED_DB_FIXTURES = ['actions', 'actionstates', 'aliases', 'executions', 'liveactions',
64
                       'policies', 'policytypes', 'rules', 'runners', 'sensors',
65
                       'triggertypes', 'triggers', 'triggerinstances', 'traces', 'apikeys',
66
                       'users', 'enforcements']
67
ALLOWED_FIXTURES = copy.copy(ALLOWED_DB_FIXTURES)
68
ALLOWED_FIXTURES.extend(['actionchains', 'workflows'])
69
70
FIXTURE_DB_MODEL = {
71
    'actions': ActionDB,
72
    'aliases': ActionAliasDB,
73
    'actionstates': ActionExecutionStateDB,
74
    'apikeys': ApiKeyDB,
75
    'enforcements': RuleEnforcementDB,
76
    'executions': ActionExecutionDB,
77
    'liveactions': LiveActionDB,
78
    'policies': PolicyDB,
79
    'policytypes': PolicyTypeDB,
80
    'rules': RuleDB,
81
    'runners': RunnerTypeDB,
82
    'sensors': SensorTypeDB,
83
    'traces': TraceDB,
84
    'triggertypes': TriggerTypeDB,
85
    'triggers': TriggerDB,
86
    'triggerinstances': TriggerInstanceDB,
87
    'users': UserDB
88
}
89
90
FIXTURE_API_MODEL = {
91
    'actions': ActionAPI,
92
    'aliases': ActionAliasAPI,
93
    'actionstates': ActionExecutionStateAPI,
94
    'apikeys': ApiKeyAPI,
95
    'enforcements': RuleEnforcementAPI,
96
    'executions': ActionExecutionAPI,
97
    'liveactions': LiveActionAPI,
98
    'policies': PolicyAPI,
99
    'policytypes': PolicyTypeAPI,
100
    'rules': RuleAPI,
101
    'runners': RunnerTypeAPI,
102
    'sensors': SensorTypeAPI,
103
    'traces': TraceAPI,
104
    'triggertypes': TriggerTypeAPI,
105
    'triggers': TriggerAPI,
106
    'triggerinstances': TriggerInstanceAPI,
107
    'users': UserAPI
108
}
109
110
111
FIXTURE_PERSISTENCE_MODEL = {
112
    'actions': Action,
113
    'aliases': ActionAlias,
114
    'actionstates': ActionExecutionState,
115
    'apikeys': ApiKey,
116
    'enforcements': RuleEnforcement,
117
    'executions': ActionExecution,
118
    'liveactions': LiveAction,
119
    'policies': Policy,
120
    'policytypes': PolicyType,
121
    'rules': Rule,
122
    'runners': RunnerType,
123
    'sensors': SensorType,
124
    'traces': Trace,
125
    'triggertypes': TriggerType,
126
    'triggers': Trigger,
127
    'triggerinstances': TriggerInstance,
128
    'users': User
129
}
130
131
GIT_SUBMODULES_NOT_CHECKED_OUT_ERROR = """
132
Git submodule "%s" is not checked out. Make sure to run "git submodule update --init
133
 --recursive" in the repository root directory to check out all the
134
submodules.
135
""".replace('\n', '').strip()
136
137
138
def get_fixtures_base_path():
139
    return os.path.join(os.path.dirname(__file__), 'fixtures')
140
141
142
def get_fixtures_packs_base_path():
143
    return os.path.join(os.path.dirname(__file__), 'fixtures/packs')
144
145
146
def get_fixtures_runners_base_path():
147
    return os.path.join(os.path.dirname(__file__), 'fixtures/packs/runners/')
148
149
150
def get_resources_base_path():
151
    return os.path.join(os.path.dirname(__file__), 'resources')
152
153
154
class FixturesLoader(object):
155
    def __init__(self):
156
        self.meta_loader = MetaLoader()
157
158
    def save_fixtures_to_db(self, fixtures_pack='generic', fixtures_dict=None,
159
                            use_object_ids=False):
160
        """
161
        Loads fixtures specified in fixtures_dict into the database
162
        and returns DB models for the fixtures.
163
164
        fixtures_dict should be of the form:
165
        {
166
            'actions': ['action-1.yaml', 'action-2.yaml'],
167
            'rules': ['rule-1.yaml'],
168
            'liveactions': ['execution-1.yaml']
169
        }
170
171
        :param fixtures_pack: Name of the pack to load fixtures from.
172
        :type fixtures_pack: ``str``
173
174
        :param fixtures_dict: Dictionary specifying the fixtures to load for each type.
175
        :type fixtures_dict: ``dict``
176
177
        :param use_object_ids: Use object id primary key from fixture file (if available) when
178
                              storing objects in the database. By default id in
179
                              file is discarded / not used and a new random one
180
                              is generated.
181
        :type use_object_ids: ``bool``
182
183
        :rtype: ``dict``
184
        """
185
        if fixtures_dict is None:
186
            fixtures_dict = {}
187
188
        fixtures_pack_path = self._validate_fixtures_pack(fixtures_pack)
189
        self._validate_fixture_dict(fixtures_dict, allowed=ALLOWED_DB_FIXTURES)
190
191
        db_models = {}
192
        for fixture_type, fixtures in six.iteritems(fixtures_dict):
193
            API_MODEL = FIXTURE_API_MODEL.get(fixture_type, None)
194
            PERSISTENCE_MODEL = FIXTURE_PERSISTENCE_MODEL.get(fixture_type, None)
195
196
            loaded_fixtures = {}
197
            for fixture in fixtures:
198
                # Guard against copy and type and similar typos
199
                if fixture in loaded_fixtures:
200
                    msg = 'Fixture "%s" is specified twice, probably a typo.' % (fixture)
201
                    raise ValueError(msg)
202
203
                fixture_dict = self.meta_loader.load(
204
                    self._get_fixture_file_path_abs(fixtures_pack_path, fixture_type, fixture))
205
                api_model = API_MODEL(**fixture_dict)
206
                db_model = API_MODEL.to_model(api_model)
207
208
                # Make sure we also set and use object id if that functionality is used
209
                if use_object_ids and 'id' in fixture_dict:
210
                    db_model.id = fixture_dict['id']
211
212
                db_model = PERSISTENCE_MODEL.add_or_update(db_model)
213
                loaded_fixtures[fixture] = db_model
214
215
            db_models[fixture_type] = loaded_fixtures
216
217
        return db_models
218
219
    def load_fixtures(self, fixtures_pack='generic', fixtures_dict=None):
220
        """
221
        Loads fixtures specified in fixtures_dict. We
222
        simply want to load the meta into dict objects.
223
224
        fixtures_dict should be of the form:
225
        {
226
            'actionchains': ['actionchain1.yaml', 'actionchain2.yaml'],
227
            'workflows': ['workflow.yaml']
228
        }
229
230
        :param fixtures_pack: Name of the pack to load fixtures from.
231
        :type fixtures_pack: ``str``
232
233
        :param fixtures_dict: Dictionary specifying the fixtures to load for each type.
234
        :type fixtures_dict: ``dict``
235
236
        :rtype: ``dict``
237
        """
238
        if not fixtures_dict:
239
            return {}
240
        fixtures_pack_path = self._validate_fixtures_pack(fixtures_pack)
241
        self._validate_fixture_dict(fixtures_dict)
242
243
        all_fixtures = {}
244
        for fixture_type, fixtures in six.iteritems(fixtures_dict):
245
            loaded_fixtures = {}
246
            for fixture in fixtures:
247
                fixture_dict = self.meta_loader.load(
248
                    self._get_fixture_file_path_abs(fixtures_pack_path, fixture_type, fixture))
249
                loaded_fixtures[fixture] = fixture_dict
250
            all_fixtures[fixture_type] = loaded_fixtures
251
252
        return all_fixtures
253
254
    def load_models(self, fixtures_pack='generic', fixtures_dict=None):
255
        """
256
        Loads fixtures specified in fixtures_dict as db models. This method must be
257
        used for fixtures that have associated DB models. We simply want to load the
258
        meta as DB models but don't want to save them to db.
259
260
        fixtures_dict should be of the form:
261
        {
262
            'actions': ['action-1.yaml', 'action-2.yaml'],
263
            'rules': ['rule-1.yaml'],
264
            'liveactions': ['execution-1.yaml']
265
        }
266
267
        :param fixtures_pack: Name of the pack to load fixtures from.
268
        :type fixtures_pack: ``str``
269
270
        :param fixtures_dict: Dictionary specifying the fixtures to load for each type.
271
        :type fixtures_dict: ``dict``
272
273
        :rtype: ``dict``
274
        """
275
        if not fixtures_dict:
276
            return {}
277
        fixtures_pack_path = self._validate_fixtures_pack(fixtures_pack)
278
        self._validate_fixture_dict(fixtures_dict, allowed=ALLOWED_DB_FIXTURES)
279
280
        all_fixtures = {}
281
        for fixture_type, fixtures in six.iteritems(fixtures_dict):
282
283
            API_MODEL = FIXTURE_API_MODEL.get(fixture_type, None)
284
285
            loaded_models = {}
286
            for fixture in fixtures:
287
                fixture_dict = self.meta_loader.load(
288
                    self._get_fixture_file_path_abs(fixtures_pack_path, fixture_type, fixture))
289
                api_model = API_MODEL(**fixture_dict)
290
                db_model = API_MODEL.to_model(api_model)
291
                loaded_models[fixture] = db_model
292
            all_fixtures[fixture_type] = loaded_models
293
294
        return all_fixtures
295
296
    def delete_fixtures_from_db(self, fixtures_pack='generic', fixtures_dict=None,
297
                                raise_on_fail=False):
298
        """
299
        Deletes fixtures specified in fixtures_dict from the database.
300
301
        fixtures_dict should be of the form:
302
        {
303
            'actions': ['action-1.yaml', 'action-2.yaml'],
304
            'rules': ['rule-1.yaml'],
305
            'liveactions': ['execution-1.yaml']
306
        }
307
308
        :param fixtures_pack: Name of the pack to delete fixtures from.
309
        :type fixtures_pack: ``str``
310
311
        :param fixtures_dict: Dictionary specifying the fixtures to delete for each type.
312
        :type fixtures_dict: ``dict``
313
314
        :param raise_on_fail: Optional If True, raises exception if delete fails on any fixture.
315
        :type raise_on_fail: ``boolean``
316
        """
317
        if not fixtures_dict:
318
            return
319
        fixtures_pack_path = self._validate_fixtures_pack(fixtures_pack)
320
        self._validate_fixture_dict(fixtures_dict)
321
322
        for fixture_type, fixtures in six.iteritems(fixtures_dict):
323
            API_MODEL = FIXTURE_API_MODEL.get(fixture_type, None)
324
            PERSISTENCE_MODEL = FIXTURE_PERSISTENCE_MODEL.get(fixture_type, None)
325
            for fixture in fixtures:
326
                fixture_dict = self.meta_loader.load(
327
                    self._get_fixture_file_path_abs(fixtures_pack_path, fixture_type, fixture))
328
                # Note that when we have a reference mechanism consistent for
329
                # every model, we can just do a get and delete the object. Until
330
                # then, this model conversions are necessary.
331
                api_model = API_MODEL(**fixture_dict)
332
                db_model = API_MODEL.to_model(api_model)
333
                try:
334
                    PERSISTENCE_MODEL.delete(db_model)
335
                except:
336
                    if raise_on_fail:
337
                        raise
338
339
    def delete_models_from_db(self, models_dict, raise_on_fail=False):
340
        """
341
        Deletes models specified in models_dict from the database.
342
343
        models_dict should be of the form:
344
        {
345
            'actions': [ACTION1, ACTION2],
346
            'rules': [RULE1],
347
            'liveactions': [EXECUTION]
348
        }
349
350
        :param fixtures_dict: Dictionary specifying the fixtures to delete for each type.
351
        :type fixtures_dict: ``dict``.
352
353
        :param raise_on_fail: Optional If True, raises exception if delete fails on any model.
354
        :type raise_on_fail: ``boolean``
355
        """
356
        for model_type, models in six.iteritems(models_dict):
357
            PERSISTENCE_MODEL = FIXTURE_PERSISTENCE_MODEL.get(model_type, None)
358
            for model in models:
359
                try:
360
                    PERSISTENCE_MODEL.delete(model)
361
                except:
362
                    if raise_on_fail:
363
                        raise
364
365
    def _validate_fixtures_pack(self, fixtures_pack):
366
        fixtures_pack_path = self._get_fixtures_pack_path(fixtures_pack)
367
368
        if not self._is_fixture_pack_exists(fixtures_pack_path):
369
            raise Exception('Fixtures pack not found ' +
370
                            'in fixtures path %s.' % get_fixtures_base_path())
371
        return fixtures_pack_path
372
373
    def _validate_fixture_dict(self, fixtures_dict, allowed=ALLOWED_FIXTURES):
374
        fixture_types = list(fixtures_dict.keys())
375
        for fixture_type in fixture_types:
376
            if fixture_type not in allowed:
377
                raise Exception('Disallowed fixture type: %s' % fixture_type)
378
379
    def _is_fixture_pack_exists(self, fixtures_pack_path):
380
        return os.path.exists(fixtures_pack_path)
381
382
    def _get_fixture_file_path_abs(self, fixtures_pack_path, fixtures_type, fixture_name):
383
        return os.path.join(fixtures_pack_path, fixtures_type, fixture_name)
384
385
    def _get_fixtures_pack_path(self, fixtures_pack_name):
386
        return os.path.join(get_fixtures_base_path(), fixtures_pack_name)
387
388
    def get_fixture_file_path_abs(self, fixtures_pack, fixtures_type, fixture_name):
389
        return os.path.join(get_fixtures_base_path(), fixtures_pack, fixtures_type, fixture_name)
390
391
392
def assert_submodules_are_checked_out():
393
    """
394
    Function which verifies that user has ran "git submodule update --init --recursive" in the
395
    root of the directory and that the "st2tests/st2tests/fixtures/packs/test" git repo submodule
396
    used by the tests is checked out.
397
    """
398
    pack_path = os.path.join(get_fixtures_packs_base_path(), 'test_content_version/')
399
    pack_path = os.path.abspath(pack_path)
400
    submodule_git_dir_or_file_path = os.path.join(pack_path, '.git')
401
402
    # NOTE: In newer versions of git, that .git is a file and not a directory
403
    if not os.path.exists(submodule_git_dir_or_file_path):
404
        raise ValueError(GIT_SUBMODULES_NOT_CHECKED_OUT_ERROR % (pack_path))
405
406
    return True
407