Coverage for watcher/db/sqlalchemy/api.py: 94%
643 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-17 12:22 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-17 12:22 +0000
1# -*- encoding: utf-8 -*-
2#
3# Copyright 2013 Hewlett-Packard Development Company, L.P.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
17"""SQLAlchemy storage backend."""
19import collections
20import datetime
21import operator
22import threading
24from oslo_config import cfg
25from oslo_db import api as oslo_db_api
26from oslo_db import exception as db_exc
27from oslo_db.sqlalchemy import enginefacade
28from oslo_db.sqlalchemy import utils as db_utils
29from oslo_utils import timeutils
30from sqlalchemy.inspection import inspect
31from sqlalchemy.orm import exc
32from sqlalchemy.orm import joinedload
34from watcher._i18n import _
35from watcher.common import exception
36from watcher.common import utils
37from watcher.db import api
38from watcher.db.sqlalchemy import models
39from watcher import objects
41CONF = cfg.CONF
43_CONTEXT = threading.local()
46def get_backend():
47 """The backend is this module itself."""
48 return Connection()
51def _session_for_read():
52 return enginefacade.reader.using(_CONTEXT)
55# NOTE(tylerchristie) Please add @oslo_db_api.retry_on_deadlock decorator to
56# any new methods using _session_for_write (as deadlocks happen on write), so
57# that oslo_db is able to retry in case of deadlocks.
58def _session_for_write():
59 return enginefacade.writer.using(_CONTEXT)
62def add_identity_filter(query, value):
63 """Adds an identity filter to a query.
65 Filters results by ID, if supplied value is a valid integer.
66 Otherwise attempts to filter results by UUID.
68 :param query: Initial query to add filter to.
69 :param value: Value for filtering results by.
70 :return: Modified query.
71 """
72 if utils.is_int_like(value):
73 return query.filter_by(id=value)
74 elif utils.is_uuid_like(value): 74 ↛ 77line 74 didn't jump to line 77 because the condition on line 74 was always true
75 return query.filter_by(uuid=value)
76 else:
77 raise exception.InvalidIdentity(identity=value)
80def _paginate_query(model, limit=None, marker=None, sort_key=None,
81 sort_dir=None, query=None):
82 sort_keys = ['id']
83 if sort_key and sort_key not in sort_keys:
84 sort_keys.insert(0, sort_key)
85 query = db_utils.paginate_query(query, model, limit, sort_keys,
86 marker=marker, sort_dir=sort_dir)
87 return query.all()
90class JoinMap(utils.Struct):
91 """Mapping for the Join-based queries"""
94NaturalJoinFilter = collections.namedtuple(
95 'NaturalJoinFilter', ['join_fieldname', 'join_model'])
98class Connection(api.BaseConnection):
99 """SqlAlchemy connection."""
101 valid_operators = {
102 "": operator.eq,
103 "eq": operator.eq,
104 "neq": operator.ne,
105 "gt": operator.gt,
106 "gte": operator.ge,
107 "lt": operator.lt,
108 "lte": operator.le,
109 "in": lambda field, choices: field.in_(choices),
110 "notin": lambda field, choices: field.notin_(choices),
111 }
113 def __init__(self):
114 super(Connection, self).__init__()
116 def __add_simple_filter(self, query, model, fieldname, value, operator_):
117 field = getattr(model, fieldname)
119 if (fieldname != 'deleted' and value and
120 field.type.python_type is datetime.datetime):
121 if not isinstance(value, datetime.datetime):
122 value = timeutils.parse_isotime(value)
124 return query.filter(self.valid_operators[operator_](field, value))
126 def __add_join_filter(self, query, model, fieldname, value, operator_):
127 query = query.join(model)
128 return self.__add_simple_filter(query, model, fieldname,
129 value, operator_)
131 def __decompose_filter(self, raw_fieldname):
132 """Decompose a filter name into its 2 subparts
134 A filter can take 2 forms:
136 - "<FIELDNAME>" which is a syntactic sugar for "<FIELDNAME>__eq"
137 - "<FIELDNAME>__<OPERATOR>" where <OPERATOR> is the comparison operator
138 to be used.
140 Available operators are:
142 - eq
143 - neq
144 - gt
145 - gte
146 - lt
147 - lte
148 - in
149 - notin
150 """
151 separator = '__'
152 fieldname, separator, operator_ = raw_fieldname.partition(separator)
154 if operator_ and operator_ not in self.valid_operators: 154 ↛ 155line 154 didn't jump to line 155 because the condition on line 154 was never true
155 raise exception.InvalidOperator(
156 operator=operator_, valid_operators=self.valid_operators)
158 return fieldname, operator_
160 def _add_filters(self, query, model, filters=None,
161 plain_fields=None, join_fieldmap=None):
162 """Generic way to add filters to a Watcher model
164 Each filter key provided by the `filters` parameter will be decomposed
165 into 2 pieces: the field name and the comparison operator
167 - "": By default, the "eq" is applied if no operator is provided
168 - "eq", which stands for "equal" : e.g. {"state__eq": "PENDING"}
169 will result in the "WHERE state = 'PENDING'" clause.
170 - "neq", which stands for "not equal" : e.g. {"state__neq": "PENDING"}
171 will result in the "WHERE state != 'PENDING'" clause.
172 - "gt", which stands for "greater than" : e.g.
173 {"created_at__gt": "2016-06-06T10:33:22.063176"} will result in the
174 "WHERE created_at > '2016-06-06T10:33:22.063176'" clause.
175 - "gte", which stands for "greater than or equal to" : e.g.
176 {"created_at__gte": "2016-06-06T10:33:22.063176"} will result in the
177 "WHERE created_at >= '2016-06-06T10:33:22.063176'" clause.
178 - "lt", which stands for "less than" : e.g.
179 {"created_at__lt": "2016-06-06T10:33:22.063176"} will result in the
180 "WHERE created_at < '2016-06-06T10:33:22.063176'" clause.
181 - "lte", which stands for "less than or equal to" : e.g.
182 {"created_at__lte": "2016-06-06T10:33:22.063176"} will result in the
183 "WHERE created_at <= '2016-06-06T10:33:22.063176'" clause.
184 - "in": e.g. {"state__in": ('SUCCEEDED', 'FAILED')} will result in the
185 "WHERE state IN ('SUCCEEDED', 'FAILED')" clause.
187 :param query: a :py:class:`sqlalchemy.orm.query.Query` instance
188 :param model: the model class the filters should relate to
189 :param filters: dict with the following structure {"fieldname": value}
190 :param plain_fields: a :py:class:`sqlalchemy.orm.query.Query` instance
191 :param join_fieldmap: a :py:class:`sqlalchemy.orm.query.Query` instance
192 """
193 soft_delete_mixin_fields = ['deleted', 'deleted_at']
194 timestamp_mixin_fields = ['created_at', 'updated_at']
195 filters = filters or {}
197 # Special case for 'deleted' because it is a non-boolean flag
198 if 'deleted' in filters:
199 deleted_filter = filters.pop('deleted')
200 op = 'eq' if not bool(deleted_filter) else 'neq'
201 filters['deleted__%s' % op] = 0
203 plain_fields = tuple(
204 (list(plain_fields) or []) +
205 soft_delete_mixin_fields +
206 timestamp_mixin_fields)
207 join_fieldmap = join_fieldmap or {}
209 for raw_fieldname, value in filters.items():
210 fieldname, operator_ = self.__decompose_filter(raw_fieldname)
211 if fieldname in plain_fields:
212 query = self.__add_simple_filter(
213 query, model, fieldname, value, operator_)
214 elif fieldname in join_fieldmap:
215 join_field, join_model = join_fieldmap[fieldname]
216 query = self.__add_join_filter(
217 query, join_model, join_field, value, operator_)
219 return query
221 @staticmethod
222 def _get_relationships(model):
223 return inspect(model).relationships
225 @staticmethod
226 def _set_eager_options(model, query):
227 relationships = inspect(model).relationships
228 for relationship in relationships:
229 if not relationship.uselist: 229 ↛ 228line 229 didn't jump to line 228 because the condition on line 229 was always true
230 # We have a One-to-X relationship
231 query = query.options(joinedload(
232 getattr(model, relationship.key)))
233 return query
235 @oslo_db_api.retry_on_deadlock
236 def _create(self, model, values):
237 with _session_for_write() as session:
238 obj = model()
239 cleaned_values = {k: v for k, v in values.items()
240 if k not in self._get_relationships(model)}
241 obj.update(cleaned_values)
242 session.add(obj)
243 session.flush()
244 return obj
246 def _get(self, context, model, fieldname, value, eager):
247 with _session_for_read() as session:
248 query = session.query(model)
249 if eager:
250 query = self._set_eager_options(model, query)
252 query = query.filter(getattr(model, fieldname) == value)
253 if not context.show_deleted:
254 query = query.filter(model.deleted_at.is_(None))
256 try:
257 obj = query.one()
258 except exc.NoResultFound:
259 raise exception.ResourceNotFound(name=model.__name__, id=value)
261 return obj
263 @staticmethod
264 @oslo_db_api.retry_on_deadlock
265 def _update(model, id_, values):
266 with _session_for_write() as session:
267 query = session.query(model)
268 query = add_identity_filter(query, id_)
269 try:
270 ref = query.with_for_update().one()
271 except exc.NoResultFound:
272 raise exception.ResourceNotFound(name=model.__name__, id=id_)
274 ref.update(values)
276 return ref
278 @staticmethod
279 @oslo_db_api.retry_on_deadlock
280 def _soft_delete(model, id_):
281 with _session_for_write() as session:
282 query = session.query(model)
283 query = add_identity_filter(query, id_)
284 try:
285 row = query.one()
286 except exc.NoResultFound:
287 raise exception.ResourceNotFound(name=model.__name__, id=id_)
289 row.soft_delete(session)
291 return row
293 @staticmethod
294 @oslo_db_api.retry_on_deadlock
295 def _destroy(model, id_):
296 with _session_for_write() as session:
297 query = session.query(model)
298 query = add_identity_filter(query, id_)
300 try:
301 query.one()
302 except exc.NoResultFound:
303 raise exception.ResourceNotFound(name=model.__name__, id=id_)
305 query.delete()
307 def _get_model_list(self, model, add_filters_func, context, filters=None,
308 limit=None, marker=None, sort_key=None, sort_dir=None,
309 eager=False):
310 with _session_for_read() as session:
311 query = session.query(model)
312 if eager:
313 query = self._set_eager_options(model, query)
314 query = add_filters_func(query, filters)
315 if not context.show_deleted:
316 query = query.filter(model.deleted_at.is_(None))
317 return _paginate_query(model, limit, marker,
318 sort_key, sort_dir, query)
320 # NOTE(erakli): _add_..._filters methods should be refactored to have same
321 # content. join_fieldmap should be filled with JoinMap instead of dict
323 def _add_goals_filters(self, query, filters):
324 if filters is None:
325 filters = {}
327 plain_fields = ['uuid', 'name', 'display_name']
329 return self._add_filters(
330 query=query, model=models.Goal, filters=filters,
331 plain_fields=plain_fields)
333 def _add_strategies_filters(self, query, filters):
334 plain_fields = ['uuid', 'name', 'display_name', 'goal_id']
335 join_fieldmap = JoinMap(
336 goal_uuid=NaturalJoinFilter(
337 join_fieldname="uuid", join_model=models.Goal),
338 goal_name=NaturalJoinFilter(
339 join_fieldname="name", join_model=models.Goal))
340 return self._add_filters(
341 query=query, model=models.Strategy, filters=filters,
342 plain_fields=plain_fields, join_fieldmap=join_fieldmap)
344 def _add_audit_templates_filters(self, query, filters):
345 if filters is None:
346 filters = {}
348 plain_fields = ['uuid', 'name', 'goal_id', 'strategy_id']
349 join_fieldmap = JoinMap(
350 goal_uuid=NaturalJoinFilter(
351 join_fieldname="uuid", join_model=models.Goal),
352 goal_name=NaturalJoinFilter(
353 join_fieldname="name", join_model=models.Goal),
354 strategy_uuid=NaturalJoinFilter(
355 join_fieldname="uuid", join_model=models.Strategy),
356 strategy_name=NaturalJoinFilter(
357 join_fieldname="name", join_model=models.Strategy),
358 )
360 return self._add_filters(
361 query=query, model=models.AuditTemplate, filters=filters,
362 plain_fields=plain_fields, join_fieldmap=join_fieldmap)
364 def _add_audits_filters(self, query, filters):
365 if filters is None:
366 filters = {}
368 plain_fields = ['uuid', 'audit_type', 'state', 'goal_id',
369 'strategy_id', 'hostname']
370 join_fieldmap = {
371 'goal_uuid': ("uuid", models.Goal),
372 'goal_name': ("name", models.Goal),
373 'strategy_uuid': ("uuid", models.Strategy),
374 'strategy_name': ("name", models.Strategy),
375 }
377 return self._add_filters(
378 query=query, model=models.Audit, filters=filters,
379 plain_fields=plain_fields, join_fieldmap=join_fieldmap)
381 def _add_action_plans_filters(self, query, filters):
382 if filters is None:
383 filters = {}
385 plain_fields = ['uuid', 'state', 'audit_id', 'strategy_id']
386 join_fieldmap = JoinMap(
387 audit_uuid=NaturalJoinFilter(
388 join_fieldname="uuid", join_model=models.Audit),
389 strategy_uuid=NaturalJoinFilter(
390 join_fieldname="uuid", join_model=models.Strategy),
391 strategy_name=NaturalJoinFilter(
392 join_fieldname="name", join_model=models.Strategy),
393 )
395 return self._add_filters(
396 query=query, model=models.ActionPlan, filters=filters,
397 plain_fields=plain_fields, join_fieldmap=join_fieldmap)
399 def _add_actions_filters(self, query, filters):
400 if filters is None:
401 filters = {}
403 plain_fields = ['uuid', 'state', 'action_plan_id']
404 join_fieldmap = {
405 'action_plan_uuid': ("uuid", models.ActionPlan),
406 }
408 query = self._add_filters(
409 query=query, model=models.Action, filters=filters,
410 plain_fields=plain_fields, join_fieldmap=join_fieldmap)
412 if 'audit_uuid' in filters:
413 with _session_for_read() as session:
414 stmt = session.query(models.ActionPlan).join(
415 models.Audit,
416 models.Audit.id == models.ActionPlan.audit_id)\
417 .filter_by(uuid=filters['audit_uuid']).subquery()
418 query = query.filter_by(action_plan_id=stmt.c.id)
420 return query
422 def _add_efficacy_indicators_filters(self, query, filters):
423 if filters is None:
424 filters = {}
426 plain_fields = ['uuid', 'name', 'unit', 'schema', 'action_plan_id']
427 join_fieldmap = JoinMap(
428 action_plan_uuid=NaturalJoinFilter(
429 join_fieldname="uuid", join_model=models.ActionPlan),
430 )
432 return self._add_filters(
433 query=query, model=models.EfficacyIndicator, filters=filters,
434 plain_fields=plain_fields, join_fieldmap=join_fieldmap)
436 def _add_scoring_engine_filters(self, query, filters):
437 if filters is None:
438 filters = {}
440 plain_fields = ['id', 'description']
442 return self._add_filters(
443 query=query, model=models.ScoringEngine, filters=filters,
444 plain_fields=plain_fields)
446 def _add_action_descriptions_filters(self, query, filters):
447 if not filters:
448 filters = {}
450 plain_fields = ['id', 'action_type']
452 return self._add_filters(
453 query=query, model=models.ActionDescription, filters=filters,
454 plain_fields=plain_fields)
456 def _add_services_filters(self, query, filters):
457 if not filters:
458 filters = {}
460 plain_fields = ['id', 'name', 'host']
462 return self._add_filters(
463 query=query, model=models.Service, filters=filters,
464 plain_fields=plain_fields)
466 # ### GOALS ### #
468 def get_goal_list(self, *args, **kwargs):
469 return self._get_model_list(models.Goal,
470 self._add_goals_filters,
471 *args, **kwargs)
473 def create_goal(self, values):
474 # ensure defaults are present for new goals
475 if not values.get('uuid'):
476 values['uuid'] = utils.generate_uuid()
478 try:
479 goal = self._create(models.Goal, values)
480 except db_exc.DBDuplicateEntry:
481 raise exception.GoalAlreadyExists(uuid=values['uuid'])
482 return goal
484 def _get_goal(self, context, fieldname, value, eager):
485 try:
486 return self._get(context, model=models.Goal,
487 fieldname=fieldname, value=value, eager=eager)
488 except exception.ResourceNotFound:
489 raise exception.GoalNotFound(goal=value)
491 def get_goal_by_id(self, context, goal_id, eager=False):
492 return self._get_goal(
493 context, fieldname="id", value=goal_id, eager=eager)
495 def get_goal_by_uuid(self, context, goal_uuid, eager=False):
496 return self._get_goal(
497 context, fieldname="uuid", value=goal_uuid, eager=eager)
499 def get_goal_by_name(self, context, goal_name, eager=False):
500 return self._get_goal(
501 context, fieldname="name", value=goal_name, eager=eager)
503 def destroy_goal(self, goal_id):
504 try:
505 return self._destroy(models.Goal, goal_id)
506 except exception.ResourceNotFound:
507 raise exception.GoalNotFound(goal=goal_id)
509 def update_goal(self, goal_id, values):
510 if 'uuid' in values:
511 raise exception.Invalid(
512 message=_("Cannot overwrite UUID for an existing Goal."))
514 try:
515 return self._update(models.Goal, goal_id, values)
516 except exception.ResourceNotFound:
517 raise exception.GoalNotFound(goal=goal_id)
519 def soft_delete_goal(self, goal_id):
520 try:
521 return self._soft_delete(models.Goal, goal_id)
522 except exception.ResourceNotFound:
523 raise exception.GoalNotFound(goal=goal_id)
525 # ### STRATEGIES ### #
527 def get_strategy_list(self, *args, **kwargs):
528 return self._get_model_list(models.Strategy,
529 self._add_strategies_filters,
530 *args, **kwargs)
532 def create_strategy(self, values):
533 # ensure defaults are present for new strategies
534 if not values.get('uuid'):
535 values['uuid'] = utils.generate_uuid()
537 try:
538 strategy = self._create(models.Strategy, values)
539 except db_exc.DBDuplicateEntry:
540 raise exception.StrategyAlreadyExists(uuid=values['uuid'])
541 return strategy
543 def _get_strategy(self, context, fieldname, value, eager):
544 try:
545 return self._get(context, model=models.Strategy,
546 fieldname=fieldname, value=value, eager=eager)
547 except exception.ResourceNotFound:
548 raise exception.StrategyNotFound(strategy=value)
550 def get_strategy_by_id(self, context, strategy_id, eager=False):
551 return self._get_strategy(
552 context, fieldname="id", value=strategy_id, eager=eager)
554 def get_strategy_by_uuid(self, context, strategy_uuid, eager=False):
555 return self._get_strategy(
556 context, fieldname="uuid", value=strategy_uuid, eager=eager)
558 def get_strategy_by_name(self, context, strategy_name, eager=False):
559 return self._get_strategy(
560 context, fieldname="name", value=strategy_name, eager=eager)
562 def destroy_strategy(self, strategy_id):
563 try:
564 return self._destroy(models.Strategy, strategy_id)
565 except exception.ResourceNotFound:
566 raise exception.StrategyNotFound(strategy=strategy_id)
568 def update_strategy(self, strategy_id, values):
569 if 'uuid' in values:
570 raise exception.Invalid(
571 message=_("Cannot overwrite UUID for an existing Strategy."))
573 try:
574 return self._update(models.Strategy, strategy_id, values)
575 except exception.ResourceNotFound:
576 raise exception.StrategyNotFound(strategy=strategy_id)
578 def soft_delete_strategy(self, strategy_id):
579 try:
580 return self._soft_delete(models.Strategy, strategy_id)
581 except exception.ResourceNotFound:
582 raise exception.StrategyNotFound(strategy=strategy_id)
584 # ### AUDIT TEMPLATES ### #
586 def get_audit_template_list(self, *args, **kwargs):
587 return self._get_model_list(models.AuditTemplate,
588 self._add_audit_templates_filters,
589 *args, **kwargs)
591 def create_audit_template(self, values):
592 # ensure defaults are present for new audit_templates
593 if not values.get('uuid'):
594 values['uuid'] = utils.generate_uuid()
596 with _session_for_write() as session:
597 query = session.query(models.AuditTemplate)
598 query = query.filter_by(name=values.get('name'),
599 deleted_at=None)
601 if len(query.all()) > 0:
602 raise exception.AuditTemplateAlreadyExists(
603 audit_template=values['name'])
605 try:
606 audit_template = self._create(models.AuditTemplate, values)
607 except db_exc.DBDuplicateEntry:
608 raise exception.AuditTemplateAlreadyExists(
609 audit_template=values['name'])
610 return audit_template
612 def _get_audit_template(self, context, fieldname, value, eager):
613 try:
614 return self._get(context, model=models.AuditTemplate,
615 fieldname=fieldname, value=value, eager=eager)
616 except exception.ResourceNotFound:
617 raise exception.AuditTemplateNotFound(audit_template=value)
619 def get_audit_template_by_id(self, context, audit_template_id,
620 eager=False):
621 return self._get_audit_template(
622 context, fieldname="id", value=audit_template_id, eager=eager)
624 def get_audit_template_by_uuid(self, context, audit_template_uuid,
625 eager=False):
626 return self._get_audit_template(
627 context, fieldname="uuid", value=audit_template_uuid, eager=eager)
629 def get_audit_template_by_name(self, context, audit_template_name,
630 eager=False):
631 return self._get_audit_template(
632 context, fieldname="name", value=audit_template_name, eager=eager)
634 def destroy_audit_template(self, audit_template_id):
635 try:
636 return self._destroy(models.AuditTemplate, audit_template_id)
637 except exception.ResourceNotFound:
638 raise exception.AuditTemplateNotFound(
639 audit_template=audit_template_id)
641 def update_audit_template(self, audit_template_id, values):
642 if 'uuid' in values:
643 raise exception.Invalid(
644 message=_("Cannot overwrite UUID for an existing "
645 "Audit Template."))
646 try:
647 return self._update(
648 models.AuditTemplate, audit_template_id, values)
649 except exception.ResourceNotFound:
650 raise exception.AuditTemplateNotFound(
651 audit_template=audit_template_id)
653 def soft_delete_audit_template(self, audit_template_id):
654 try:
655 return self._soft_delete(models.AuditTemplate, audit_template_id)
656 except exception.ResourceNotFound:
657 raise exception.AuditTemplateNotFound(
658 audit_template=audit_template_id)
660 # ### AUDITS ### #
662 def get_audit_list(self, *args, **kwargs):
663 return self._get_model_list(models.Audit,
664 self._add_audits_filters,
665 *args, **kwargs)
667 def create_audit(self, values):
668 # ensure defaults are present for new audits
669 if not values.get('uuid'):
670 values['uuid'] = utils.generate_uuid()
672 with _session_for_write() as session:
673 query = session.query(models.Audit)
674 query = query.filter_by(name=values.get('name'),
675 deleted_at=None)
677 if len(query.all()) > 0:
678 raise exception.AuditAlreadyExists(
679 audit=values['name'])
681 if values.get('state') is None:
682 values['state'] = objects.audit.State.PENDING
684 if not values.get('auto_trigger'):
685 values['auto_trigger'] = False
687 try:
688 audit = self._create(models.Audit, values)
689 except db_exc.DBDuplicateEntry:
690 raise exception.AuditAlreadyExists(audit=values['uuid'])
691 return audit
693 def _get_audit(self, context, fieldname, value, eager):
694 try:
695 return self._get(context, model=models.Audit,
696 fieldname=fieldname, value=value, eager=eager)
697 except exception.ResourceNotFound:
698 raise exception.AuditNotFound(audit=value)
700 def get_audit_by_id(self, context, audit_id, eager=False):
701 return self._get_audit(
702 context, fieldname="id", value=audit_id, eager=eager)
704 def get_audit_by_uuid(self, context, audit_uuid, eager=False):
705 return self._get_audit(
706 context, fieldname="uuid", value=audit_uuid, eager=eager)
708 def get_audit_by_name(self, context, audit_name, eager=False):
709 return self._get_audit(
710 context, fieldname="name", value=audit_name, eager=eager)
712 def destroy_audit(self, audit_id):
713 def is_audit_referenced(session, audit_id):
714 """Checks whether the audit is referenced by action_plan(s)."""
715 query = session.query(models.ActionPlan)
716 query = self._add_action_plans_filters(
717 query, {'audit_id': audit_id})
718 return query.count() != 0
720 with _session_for_write() as session:
721 query = session.query(models.Audit)
722 query = add_identity_filter(query, audit_id)
724 try:
725 audit_ref = query.one()
726 except exc.NoResultFound:
727 raise exception.AuditNotFound(audit=audit_id)
729 if is_audit_referenced(session, audit_ref['id']):
730 raise exception.AuditReferenced(audit=audit_id)
732 query.delete()
734 def update_audit(self, audit_id, values):
735 if 'uuid' in values:
736 raise exception.Invalid(
737 message=_("Cannot overwrite UUID for an existing "
738 "Audit."))
740 try:
741 return self._update(models.Audit, audit_id, values)
742 except exception.ResourceNotFound:
743 raise exception.AuditNotFound(audit=audit_id)
745 def soft_delete_audit(self, audit_id):
746 try:
747 return self._soft_delete(models.Audit, audit_id)
748 except exception.ResourceNotFound:
749 raise exception.AuditNotFound(audit=audit_id)
751 # ### ACTIONS ### #
753 def get_action_list(self, *args, **kwargs):
754 return self._get_model_list(models.Action,
755 self._add_actions_filters,
756 *args, **kwargs)
758 def create_action(self, values):
759 # ensure defaults are present for new actions
760 if not values.get('uuid'):
761 values['uuid'] = utils.generate_uuid()
763 if values.get('state') is None:
764 values['state'] = objects.action.State.PENDING
766 try:
767 action = self._create(models.Action, values)
768 except db_exc.DBDuplicateEntry:
769 raise exception.ActionAlreadyExists(uuid=values['uuid'])
770 return action
772 def _get_action(self, context, fieldname, value, eager):
773 try:
774 return self._get(context, model=models.Action,
775 fieldname=fieldname, value=value, eager=eager)
776 except exception.ResourceNotFound:
777 raise exception.ActionNotFound(action=value)
779 def get_action_by_id(self, context, action_id, eager=False):
780 return self._get_action(
781 context, fieldname="id", value=action_id, eager=eager)
783 def get_action_by_uuid(self, context, action_uuid, eager=False):
784 return self._get_action(
785 context, fieldname="uuid", value=action_uuid, eager=eager)
787 def destroy_action(self, action_id):
788 with _session_for_write() as session:
789 query = session.query(models.Action)
790 query = add_identity_filter(query, action_id)
791 count = query.delete()
792 if count != 1:
793 raise exception.ActionNotFound(action_id)
795 def update_action(self, action_id, values):
796 # NOTE(dtantsur): this can lead to very strange errors
797 if 'uuid' in values:
798 raise exception.Invalid(
799 message=_("Cannot overwrite UUID for an existing Action."))
801 return self._do_update_action(action_id, values)
803 @staticmethod
804 def _do_update_action(action_id, values):
805 with _session_for_write() as session:
806 query = session.query(models.Action)
807 query = add_identity_filter(query, action_id)
808 try:
809 ref = query.with_for_update().one()
810 except exc.NoResultFound:
811 raise exception.ActionNotFound(action=action_id)
813 ref.update(values)
814 return ref
816 def soft_delete_action(self, action_id):
817 try:
818 return self._soft_delete(models.Action, action_id)
819 except exception.ResourceNotFound:
820 raise exception.ActionNotFound(action=action_id)
822 # ### ACTION PLANS ### #
824 def get_action_plan_list(self, *args, **kwargs):
825 return self._get_model_list(models.ActionPlan,
826 self._add_action_plans_filters,
827 *args, **kwargs)
829 def create_action_plan(self, values):
830 # ensure defaults are present for new audits
831 if not values.get('uuid'):
832 values['uuid'] = utils.generate_uuid()
834 try:
835 action_plan = self._create(models.ActionPlan, values)
836 except db_exc.DBDuplicateEntry:
837 raise exception.ActionPlanAlreadyExists(uuid=values['uuid'])
838 return action_plan
840 def _get_action_plan(self, context, fieldname, value, eager):
841 try:
842 return self._get(context, model=models.ActionPlan,
843 fieldname=fieldname, value=value, eager=eager)
844 except exception.ResourceNotFound:
845 raise exception.ActionPlanNotFound(action_plan=value)
847 def get_action_plan_by_id(self, context, action_plan_id, eager=False):
848 return self._get_action_plan(
849 context, fieldname="id", value=action_plan_id, eager=eager)
851 def get_action_plan_by_uuid(self, context, action_plan_uuid, eager=False):
852 return self._get_action_plan(
853 context, fieldname="uuid", value=action_plan_uuid, eager=eager)
855 def destroy_action_plan(self, action_plan_id):
856 def is_action_plan_referenced(session, action_plan_id):
857 """Checks whether the action_plan is referenced by action(s)."""
858 query = session.query(models.Action)
859 query = self._add_actions_filters(
860 query, {'action_plan_id': action_plan_id})
861 return query.count() != 0
863 with _session_for_write() as session:
864 query = session.query(models.ActionPlan)
865 query = add_identity_filter(query, action_plan_id)
867 try:
868 action_plan_ref = query.one()
869 except exc.NoResultFound:
870 raise exception.ActionPlanNotFound(action_plan=action_plan_id)
872 if is_action_plan_referenced(session, action_plan_ref['id']):
873 raise exception.ActionPlanReferenced(
874 action_plan=action_plan_id)
876 query.delete()
878 def update_action_plan(self, action_plan_id, values):
879 if 'uuid' in values:
880 raise exception.Invalid(
881 message=_("Cannot overwrite UUID for an existing "
882 "Action Plan."))
884 return self._do_update_action_plan(action_plan_id, values)
886 @staticmethod
887 def _do_update_action_plan(action_plan_id, values):
888 with _session_for_write() as session:
889 query = session.query(models.ActionPlan)
890 query = add_identity_filter(query, action_plan_id)
891 try:
892 ref = query.with_for_update().one()
893 except exc.NoResultFound:
894 raise exception.ActionPlanNotFound(action_plan=action_plan_id)
896 ref.update(values)
897 return ref
899 def soft_delete_action_plan(self, action_plan_id):
900 try:
901 return self._soft_delete(models.ActionPlan, action_plan_id)
902 except exception.ResourceNotFound:
903 raise exception.ActionPlanNotFound(action_plan=action_plan_id)
905 # ### EFFICACY INDICATORS ### #
907 def get_efficacy_indicator_list(self, *args, **kwargs):
908 eff_ind_models = self._get_model_list(
909 models.EfficacyIndicator,
910 self._add_efficacy_indicators_filters,
911 *args, **kwargs
912 )
913 for indicator in eff_ind_models:
914 if indicator.data is not None:
915 # jgilaber: use the data value since it stores the value
916 # properly, see https://bugs.launchpad.net/watcher/+bug/2103458
917 # for more details
918 indicator.value = indicator.data
919 else:
920 # if data is None, it means that we're reading data from a
921 # database created before the 15f7375ca737 revision, use the
922 # value column
923 indicator.data = indicator.value
924 # store the new column in the database
925 self._update(models.EfficacyIndicator,
926 indicator.id, indicator)
928 return eff_ind_models
930 def create_efficacy_indicator(self, values):
931 # ensure defaults are present for new efficacy indicators
932 if not values.get('uuid'):
933 values['uuid'] = utils.generate_uuid()
935 # jgilaber: use the data column since it stores the value
936 # properly, see https://bugs.launchpad.net/watcher/+bug/2103458
937 # for more details
938 values['data'] = values.get('value')
939 try:
940 efficacy_indicator = self._create(models.EfficacyIndicator, values)
941 except db_exc.DBDuplicateEntry:
942 raise exception.EfficacyIndicatorAlreadyExists(uuid=values['uuid'])
943 return efficacy_indicator
945 def _get_efficacy_indicator(self, context, fieldname, value, eager):
946 try:
947 efficacy_indicator = self._get(context,
948 model=models.EfficacyIndicator,
949 fieldname=fieldname,
950 value=value, eager=eager)
952 if efficacy_indicator.data is not None:
953 # jgilaber: use the data value since it stores the value
954 # properly, see https://bugs.launchpad.net/watcher/+bug/2103458
955 # for more details
956 efficacy_indicator.value = efficacy_indicator.data
957 else:
958 # if data is None, it means that we're reading data from a
959 # database created before the 15f7375ca737 revision, use the
960 # value column
961 efficacy_indicator.data = efficacy_indicator.value
962 # store the new column in the database
963 self._update(models.EfficacyIndicator, efficacy_indicator.id,
964 efficacy_indicator)
965 return efficacy_indicator
966 except exception.ResourceNotFound:
967 raise exception.EfficacyIndicatorNotFound(efficacy_indicator=value)
969 def get_efficacy_indicator_by_id(self, context, efficacy_indicator_id,
970 eager=False):
971 return self._get_efficacy_indicator(
972 context, fieldname="id",
973 value=efficacy_indicator_id, eager=eager)
975 def get_efficacy_indicator_by_uuid(self, context, efficacy_indicator_uuid,
976 eager=False):
977 return self._get_efficacy_indicator(
978 context, fieldname="uuid",
979 value=efficacy_indicator_uuid, eager=eager)
981 def get_efficacy_indicator_by_name(self, context, efficacy_indicator_name,
982 eager=False):
983 return self._get_efficacy_indicator(
984 context, fieldname="name",
985 value=efficacy_indicator_name, eager=eager)
987 def update_efficacy_indicator(self, efficacy_indicator_id, values):
988 if 'uuid' in values:
989 raise exception.Invalid(
990 message=_("Cannot overwrite UUID for an existing "
991 "efficacy indicator."))
993 try:
994 return self._update(
995 models.EfficacyIndicator, efficacy_indicator_id, values)
996 except exception.ResourceNotFound:
997 raise exception.EfficacyIndicatorNotFound(
998 efficacy_indicator=efficacy_indicator_id)
1000 def soft_delete_efficacy_indicator(self, efficacy_indicator_id):
1001 try:
1002 return self._soft_delete(
1003 models.EfficacyIndicator, efficacy_indicator_id)
1004 except exception.ResourceNotFound:
1005 raise exception.EfficacyIndicatorNotFound(
1006 efficacy_indicator=efficacy_indicator_id)
1008 def destroy_efficacy_indicator(self, efficacy_indicator_id):
1009 try:
1010 return self._destroy(
1011 models.EfficacyIndicator, efficacy_indicator_id)
1012 except exception.ResourceNotFound:
1013 raise exception.EfficacyIndicatorNotFound(
1014 efficacy_indicator=efficacy_indicator_id)
1016 # ### SCORING ENGINES ### #
1018 def get_scoring_engine_list(self, *args, **kwargs):
1019 return self._get_model_list(models.ScoringEngine,
1020 self._add_scoring_engine_filters,
1021 *args, **kwargs)
1023 def create_scoring_engine(self, values):
1024 # ensure defaults are present for new scoring engines
1025 if not values.get('uuid'):
1026 values['uuid'] = utils.generate_uuid()
1028 try:
1029 scoring_engine = self._create(models.ScoringEngine, values)
1030 except db_exc.DBDuplicateEntry:
1031 raise exception.ScoringEngineAlreadyExists(uuid=values['uuid'])
1032 return scoring_engine
1034 def _get_scoring_engine(self, context, fieldname, value, eager):
1035 try:
1036 return self._get(context, model=models.ScoringEngine,
1037 fieldname=fieldname, value=value, eager=eager)
1038 except exception.ResourceNotFound:
1039 raise exception.ScoringEngineNotFound(scoring_engine=value)
1041 def get_scoring_engine_by_id(self, context, scoring_engine_id,
1042 eager=False):
1043 return self._get_scoring_engine(
1044 context, fieldname="id", value=scoring_engine_id, eager=eager)
1046 def get_scoring_engine_by_uuid(self, context, scoring_engine_uuid,
1047 eager=False):
1048 return self._get_scoring_engine(
1049 context, fieldname="uuid", value=scoring_engine_uuid, eager=eager)
1051 def get_scoring_engine_by_name(self, context, scoring_engine_name,
1052 eager=False):
1053 return self._get_scoring_engine(
1054 context, fieldname="name", value=scoring_engine_name, eager=eager)
1056 def destroy_scoring_engine(self, scoring_engine_id):
1057 try:
1058 return self._destroy(models.ScoringEngine, scoring_engine_id)
1059 except exception.ResourceNotFound:
1060 raise exception.ScoringEngineNotFound(
1061 scoring_engine=scoring_engine_id)
1063 def update_scoring_engine(self, scoring_engine_id, values):
1064 if 'uuid' in values:
1065 raise exception.Invalid(
1066 message=_("Cannot overwrite UUID for an existing "
1067 "Scoring Engine."))
1069 try:
1070 return self._update(
1071 models.ScoringEngine, scoring_engine_id, values)
1072 except exception.ResourceNotFound:
1073 raise exception.ScoringEngineNotFound(
1074 scoring_engine=scoring_engine_id)
1076 def soft_delete_scoring_engine(self, scoring_engine_id):
1077 try:
1078 return self._soft_delete(
1079 models.ScoringEngine, scoring_engine_id)
1080 except exception.ResourceNotFound:
1081 raise exception.ScoringEngineNotFound(
1082 scoring_engine=scoring_engine_id)
1084 # ### SERVICES ### #
1086 def get_service_list(self, *args, **kwargs):
1087 return self._get_model_list(models.Service,
1088 self._add_services_filters,
1089 *args, **kwargs)
1091 def create_service(self, values):
1092 try:
1093 service = self._create(models.Service, values)
1094 except db_exc.DBDuplicateEntry:
1095 raise exception.ServiceAlreadyExists(name=values['name'],
1096 host=values['host'])
1097 return service
1099 def _get_service(self, context, fieldname, value, eager):
1100 try:
1101 return self._get(context, model=models.Service,
1102 fieldname=fieldname, value=value, eager=eager)
1103 except exception.ResourceNotFound:
1104 raise exception.ServiceNotFound(service=value)
1106 def get_service_by_id(self, context, service_id, eager=False):
1107 return self._get_service(
1108 context, fieldname="id", value=service_id, eager=eager)
1110 def get_service_by_name(self, context, service_name, eager=False):
1111 return self._get_service(
1112 context, fieldname="name", value=service_name, eager=eager)
1114 def destroy_service(self, service_id):
1115 try:
1116 return self._destroy(models.Service, service_id)
1117 except exception.ResourceNotFound:
1118 raise exception.ServiceNotFound(service=service_id)
1120 def update_service(self, service_id, values):
1121 try:
1122 return self._update(models.Service, service_id, values)
1123 except exception.ResourceNotFound:
1124 raise exception.ServiceNotFound(service=service_id)
1126 def soft_delete_service(self, service_id):
1127 try:
1128 return self._soft_delete(models.Service, service_id)
1129 except exception.ResourceNotFound:
1130 raise exception.ServiceNotFound(service=service_id)
1132 # ### ACTION_DESCRIPTIONS ### #
1134 def get_action_description_list(self, *args, **kwargs):
1135 return self._get_model_list(models.ActionDescription,
1136 self._add_action_descriptions_filters,
1137 *args, **kwargs)
1139 def create_action_description(self, values):
1140 try:
1141 action_description = self._create(models.ActionDescription, values)
1142 except db_exc.DBDuplicateEntry:
1143 raise exception.ActionDescriptionAlreadyExists(
1144 action_type=values['action_type'])
1145 return action_description
1147 def _get_action_description(self, context, fieldname, value, eager):
1148 try:
1149 return self._get(context, model=models.ActionDescription,
1150 fieldname=fieldname, value=value, eager=eager)
1151 except exception.ResourceNotFound:
1152 raise exception.ActionDescriptionNotFound(action_id=value)
1154 def get_action_description_by_id(self, context,
1155 action_id, eager=False):
1156 return self._get_action_description(
1157 context, fieldname="id", value=action_id, eager=eager)
1159 def get_action_description_by_type(self, context,
1160 action_type, eager=False):
1161 return self._get_action_description(
1162 context, fieldname="action_type", value=action_type, eager=eager)
1164 def destroy_action_description(self, action_id):
1165 try:
1166 return self._destroy(models.ActionDescription, action_id)
1167 except exception.ResourceNotFound:
1168 raise exception.ActionDescriptionNotFound(
1169 action_id=action_id)
1171 def update_action_description(self, action_id, values):
1172 try:
1173 return self._update(models.ActionDescription,
1174 action_id, values)
1175 except exception.ResourceNotFound:
1176 raise exception.ActionDescriptionNotFound(
1177 action_id=action_id)
1179 def soft_delete_action_description(self, action_id):
1180 try:
1181 return self._soft_delete(models.ActionDescription, action_id)
1182 except exception.ResourceNotFound:
1183 raise exception.ActionDescriptionNotFound(
1184 action_id=action_id)