Refactored Watcher objects to use OVO
In this changeset, I modified all existing Watcher objects to now rely on oslo.versionedobjects as a base. Change-Id: I3c9b1ca6da529d128743b99020350f28926ea1a2 Partially-Implements: blueprint watcher-versioned-objects
This commit is contained in:
@@ -16,6 +16,9 @@ import sys
|
||||
import os
|
||||
|
||||
from watcher import version as watcher_version
|
||||
from watcher import objects
|
||||
|
||||
objects.register_all()
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
|
@@ -21,6 +21,7 @@ oslo.reports>=0.6.0 # Apache-2.0
|
||||
oslo.serialization>=1.10.0 # Apache-2.0
|
||||
oslo.service>=1.10.0 # Apache-2.0
|
||||
oslo.utils>=3.16.0 # Apache-2.0
|
||||
oslo.versionedobjects>=1.13.0 # Apache-2.0
|
||||
PasteDeploy>=1.5.0 # MIT
|
||||
pbr>=1.6 # Apache-2.0
|
||||
pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD
|
||||
|
@@ -379,7 +379,7 @@ class ActionsController(rest.RestController):
|
||||
action_dict = action.as_dict()
|
||||
context = pecan.request.context
|
||||
new_action = objects.Action(context, **action_dict)
|
||||
new_action.create(context)
|
||||
new_action.create()
|
||||
|
||||
# Set the HTTP Location Header
|
||||
pecan.response.location = link.build_url('actions', new_action.uuid)
|
||||
|
@@ -519,7 +519,7 @@ class AuditsController(rest.RestController):
|
||||
audit_dict = audit.as_dict()
|
||||
|
||||
new_audit = objects.Audit(context, **audit_dict)
|
||||
new_audit.create(context)
|
||||
new_audit.create()
|
||||
|
||||
# Set the HTTP Location Header
|
||||
pecan.response.location = link.build_url('audits', new_audit.uuid)
|
||||
|
@@ -568,11 +568,11 @@ class AuditTemplatesController(rest.RestController):
|
||||
audit_template_dict = audit_template.as_dict()
|
||||
new_audit_template = objects.AuditTemplate(context,
|
||||
**audit_template_dict)
|
||||
new_audit_template.create(context)
|
||||
new_audit_template.create()
|
||||
|
||||
# Set the HTTP Location Header
|
||||
pecan.response.location = link.build_url('audit_templates',
|
||||
new_audit_template.uuid)
|
||||
pecan.response.location = link.build_url(
|
||||
'audit_templates', new_audit_template.uuid)
|
||||
return AuditTemplate.convert_with_links(new_audit_template)
|
||||
|
||||
@wsme.validate(types.uuid, [AuditTemplatePatchType])
|
||||
|
@@ -22,7 +22,7 @@ from watcher.applier.action_plan import base
|
||||
from watcher.applier import default
|
||||
from watcher.applier.messaging import event_types
|
||||
from watcher.common.messaging.events import event
|
||||
from watcher.objects import action_plan as ap_objects
|
||||
from watcher import objects
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
@@ -35,7 +35,7 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
||||
self.action_plan_uuid = action_plan_uuid
|
||||
|
||||
def notify(self, uuid, event_type, state):
|
||||
action_plan = ap_objects.ActionPlan.get_by_uuid(self.ctx, uuid)
|
||||
action_plan = objects.ActionPlan.get_by_uuid(self.ctx, uuid)
|
||||
action_plan.state = state
|
||||
action_plan.save()
|
||||
ev = event.Event()
|
||||
@@ -50,13 +50,13 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
|
||||
# update state
|
||||
self.notify(self.action_plan_uuid,
|
||||
event_types.EventTypes.LAUNCH_ACTION_PLAN,
|
||||
ap_objects.State.ONGOING)
|
||||
objects.action_plan.State.ONGOING)
|
||||
applier = default.DefaultApplier(self.ctx, self.service)
|
||||
applier.execute(self.action_plan_uuid)
|
||||
state = ap_objects.State.SUCCEEDED
|
||||
state = objects.action_plan.State.SUCCEEDED
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
state = ap_objects.State.FAILED
|
||||
state = objects.action_plan.State.FAILED
|
||||
|
||||
finally:
|
||||
# update state
|
||||
|
@@ -23,7 +23,7 @@ from taskflow import task
|
||||
from watcher._i18n import _LE, _LW, _LC
|
||||
from watcher.applier.workflow_engine import base
|
||||
from watcher.common import exception
|
||||
from watcher.objects import action as obj_action
|
||||
from watcher import objects
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
@@ -107,14 +107,12 @@ class TaskFlowActionContainer(task.Task):
|
||||
|
||||
def pre_execute(self):
|
||||
try:
|
||||
self.engine.notify(self._db_action,
|
||||
obj_action.State.ONGOING)
|
||||
self.engine.notify(self._db_action, objects.action.State.ONGOING)
|
||||
LOG.debug("Pre-condition action: %s", self.name)
|
||||
self.action.pre_condition()
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
self.engine.notify(self._db_action,
|
||||
obj_action.State.FAILED)
|
||||
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
||||
raise
|
||||
|
||||
def execute(self, *args, **kwargs):
|
||||
@@ -122,15 +120,13 @@ class TaskFlowActionContainer(task.Task):
|
||||
LOG.debug("Running action: %s", self.name)
|
||||
|
||||
self.action.execute()
|
||||
self.engine.notify(self._db_action,
|
||||
obj_action.State.SUCCEEDED)
|
||||
self.engine.notify(self._db_action, objects.action.State.SUCCEEDED)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
LOG.error(_LE('The workflow engine has failed '
|
||||
'to execute the action: %s'), self.name)
|
||||
|
||||
self.engine.notify(self._db_action,
|
||||
obj_action.State.FAILED)
|
||||
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
||||
raise
|
||||
|
||||
def post_execute(self):
|
||||
@@ -139,8 +135,7 @@ class TaskFlowActionContainer(task.Task):
|
||||
self.action.post_condition()
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
self.engine.notify(self._db_action,
|
||||
obj_action.State.FAILED)
|
||||
self.engine.notify(self._db_action, objects.action.State.FAILED)
|
||||
raise
|
||||
|
||||
def revert(self, *args, **kwargs):
|
||||
|
@@ -36,8 +36,8 @@ from watcher.common.messaging.events import event_dispatcher as dispatcher
|
||||
from watcher.common.messaging import messaging_handler
|
||||
from watcher.common import rpc
|
||||
from watcher.common import scheduling
|
||||
from watcher import objects
|
||||
from watcher.objects import base
|
||||
from watcher.objects import service as service_object
|
||||
from watcher import opts
|
||||
from watcher import version
|
||||
|
||||
@@ -118,7 +118,7 @@ class ServiceHeartbeat(scheduling.BackgroundSchedulerService):
|
||||
|
||||
def send_beat(self):
|
||||
host = CONF.host
|
||||
watcher_list = service_object.Service.list(
|
||||
watcher_list = objects.Service.list(
|
||||
self.context, filters={'name': self.service_name,
|
||||
'host': host})
|
||||
if watcher_list:
|
||||
@@ -126,7 +126,7 @@ class ServiceHeartbeat(scheduling.BackgroundSchedulerService):
|
||||
watcher_service.last_seen_up = datetime.datetime.utcnow()
|
||||
watcher_service.save()
|
||||
else:
|
||||
watcher_service = service_object.Service(self.context)
|
||||
watcher_service = objects.Service(self.context)
|
||||
watcher_service.name = self.service_name
|
||||
watcher_service.host = host
|
||||
watcher_service.create()
|
||||
@@ -333,6 +333,7 @@ def prepare_service(argv=(), conf=cfg.CONF):
|
||||
default_log_levels=_DEFAULT_LOG_LEVELS)
|
||||
log.setup(conf, 'python-watcher')
|
||||
conf.log_opt_values(LOG, logging.DEBUG)
|
||||
objects.register_all()
|
||||
|
||||
gmr.TextGuruMeditation.register_section(_('Plugins'), opts.show_plugins)
|
||||
gmr.TextGuruMeditation.setup_autorun(version, conf=conf)
|
||||
|
@@ -34,9 +34,7 @@ from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api
|
||||
from watcher.db.sqlalchemy import models
|
||||
from watcher.objects import action as action_objects
|
||||
from watcher.objects import action_plan as ap_objects
|
||||
from watcher.objects import audit as audit_objects
|
||||
from watcher import objects
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@@ -70,7 +68,6 @@ def model_query(model, *args, **kwargs):
|
||||
|
||||
:param session: if present, the session to use
|
||||
"""
|
||||
|
||||
session = kwargs.get('session') or get_session()
|
||||
query = session.query(model, *args)
|
||||
return query
|
||||
@@ -647,7 +644,7 @@ class Connection(api.BaseConnection):
|
||||
query = self._add_audits_filters(query, filters)
|
||||
if not context.show_deleted:
|
||||
query = query.filter(
|
||||
~(models.Audit.state == audit_objects.State.DELETED))
|
||||
~(models.Audit.state == objects.audit.State.DELETED))
|
||||
|
||||
return _paginate_query(models.Audit, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
@@ -658,7 +655,7 @@ class Connection(api.BaseConnection):
|
||||
values['uuid'] = utils.generate_uuid()
|
||||
|
||||
if values.get('state') is None:
|
||||
values['state'] = audit_objects.State.PENDING
|
||||
values['state'] = objects.audit.State.PENDING
|
||||
|
||||
audit = models.Audit()
|
||||
audit.update(values)
|
||||
@@ -734,7 +731,7 @@ class Connection(api.BaseConnection):
|
||||
query = self._add_actions_filters(query, filters)
|
||||
if not context.show_deleted:
|
||||
query = query.filter(
|
||||
~(models.Action.state == action_objects.State.DELETED))
|
||||
~(models.Action.state == objects.action.State.DELETED))
|
||||
return _paginate_query(models.Action, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
@@ -821,7 +818,8 @@ class Connection(api.BaseConnection):
|
||||
query = self._add_action_plans_filters(query, filters)
|
||||
if not context.show_deleted:
|
||||
query = query.filter(
|
||||
~(models.ActionPlan.state == ap_objects.State.DELETED))
|
||||
~(models.ActionPlan.state ==
|
||||
objects.action_plan.State.DELETED))
|
||||
|
||||
return _paginate_query(models.ActionPlan, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
@@ -26,7 +26,7 @@ from watcher.common.messaging.events import event as watcher_event
|
||||
from watcher.decision_engine.messaging import events as de_events
|
||||
from watcher.decision_engine.planner import manager as planner_manager
|
||||
from watcher.decision_engine.strategy.context import default as default_context
|
||||
from watcher.objects import audit as audit_objects
|
||||
from watcher import objects
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
@@ -89,13 +89,13 @@ class AuditHandler(BaseAuditHandler):
|
||||
def pre_execute(self, audit, request_context):
|
||||
LOG.debug("Trigger audit %s", audit.uuid)
|
||||
# change state of the audit to ONGOING
|
||||
self.update_audit_state(audit, audit_objects.State.ONGOING)
|
||||
self.update_audit_state(audit, objects.audit.State.ONGOING)
|
||||
|
||||
def post_execute(self, audit, solution, request_context):
|
||||
self.planner.schedule(request_context, audit.id, solution)
|
||||
|
||||
# change state of the audit to SUCCEEDED
|
||||
self.update_audit_state(audit, audit_objects.State.SUCCEEDED)
|
||||
self.update_audit_state(audit, objects.audit.State.SUCCEEDED)
|
||||
|
||||
def execute(self, audit, request_context):
|
||||
try:
|
||||
@@ -104,4 +104,4 @@ class AuditHandler(BaseAuditHandler):
|
||||
self.post_execute(audit, solution, request_context)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
self.update_audit_state(audit, audit_objects.State.FAILED)
|
||||
self.update_audit_state(audit, objects.audit.State.FAILED)
|
||||
|
@@ -25,8 +25,7 @@ from oslo_config import cfg
|
||||
|
||||
from watcher.common import context
|
||||
from watcher.decision_engine.audit import base
|
||||
from watcher.objects import action_plan as action_objects
|
||||
from watcher.objects import audit as audit_objects
|
||||
from watcher import objects
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@@ -56,11 +55,11 @@ class ContinuousAuditHandler(base.AuditHandler):
|
||||
return self._scheduler
|
||||
|
||||
def _is_audit_inactive(self, audit):
|
||||
audit = audit_objects.Audit.get_by_uuid(self.context_show_deleted,
|
||||
audit.uuid)
|
||||
if audit.state in (audit_objects.State.CANCELLED,
|
||||
audit_objects.State.DELETED,
|
||||
audit_objects.State.FAILED):
|
||||
audit = objects.Audit.get_by_uuid(
|
||||
self.context_show_deleted, audit.uuid)
|
||||
if audit.state in (objects.audit.State.CANCELLED,
|
||||
objects.audit.State.DELETED,
|
||||
objects.audit.State.FAILED):
|
||||
# if audit isn't in active states, audit's job must be removed to
|
||||
# prevent using of inactive audit in future.
|
||||
job_to_delete = [job for job in self.jobs
|
||||
@@ -77,14 +76,13 @@ class ContinuousAuditHandler(base.AuditHandler):
|
||||
solution = self.strategy_context.execute_strategy(
|
||||
audit, request_context)
|
||||
|
||||
if audit.audit_type == audit_objects.AuditType.CONTINUOUS.value:
|
||||
if audit.audit_type == objects.audit.AuditType.CONTINUOUS.value:
|
||||
a_plan_filters = {'audit_uuid': audit.uuid,
|
||||
'state': action_objects.State.RECOMMENDED}
|
||||
action_plans = action_objects.ActionPlan.list(
|
||||
request_context,
|
||||
filters=a_plan_filters)
|
||||
'state': objects.action_plan.State.RECOMMENDED}
|
||||
action_plans = objects.ActionPlan.list(
|
||||
request_context, filters=a_plan_filters)
|
||||
for plan in action_plans:
|
||||
plan.state = action_objects.State.CANCELLED
|
||||
plan.state = objects.action_plan.State.CANCELLED
|
||||
plan.save()
|
||||
return solution
|
||||
|
||||
@@ -98,13 +96,12 @@ class ContinuousAuditHandler(base.AuditHandler):
|
||||
def launch_audits_periodically(self):
|
||||
audit_context = context.RequestContext(is_admin=True)
|
||||
audit_filters = {
|
||||
'audit_type': audit_objects.AuditType.CONTINUOUS.value,
|
||||
'state__in': (audit_objects.State.PENDING,
|
||||
audit_objects.State.ONGOING,
|
||||
audit_objects.State.SUCCEEDED)
|
||||
'audit_type': objects.audit.AuditType.CONTINUOUS.value,
|
||||
'state__in': (objects.audit.State.PENDING,
|
||||
objects.audit.State.ONGOING,
|
||||
objects.audit.State.SUCCEEDED)
|
||||
}
|
||||
audits = audit_objects.Audit.list(audit_context,
|
||||
filters=audit_filters)
|
||||
audits = objects.Audit.list(audit_context, filters=audit_filters)
|
||||
scheduler_job_args = [job.args for job in self.scheduler.get_jobs()
|
||||
if job.name == 'execute_audit']
|
||||
for audit in audits:
|
||||
|
@@ -23,7 +23,7 @@ from oslo_log import log
|
||||
|
||||
from watcher.decision_engine.audit import continuous as continuous_handler
|
||||
from watcher.decision_engine.audit import oneshot as oneshot_handler
|
||||
from watcher.objects import audit as audit_objects
|
||||
from watcher import objects
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
@@ -49,7 +49,7 @@ class AuditEndpoint(object):
|
||||
return self._messaging
|
||||
|
||||
def do_trigger_audit(self, context, audit_uuid):
|
||||
audit = audit_objects.Audit.get_by_uuid(context, audit_uuid)
|
||||
audit = objects.Audit.get_by_uuid(context, audit_uuid)
|
||||
self._oneshot_handler.execute(audit, context)
|
||||
|
||||
def trigger_audit(self, context, audit_uuid):
|
||||
|
@@ -128,7 +128,7 @@ class DefaultPlanner(base.BasePlanner):
|
||||
}
|
||||
|
||||
new_action_plan = objects.ActionPlan(context, **action_plan_dict)
|
||||
new_action_plan.create(context)
|
||||
new_action_plan.create()
|
||||
|
||||
return new_action_plan
|
||||
|
||||
@@ -145,7 +145,7 @@ class DefaultPlanner(base.BasePlanner):
|
||||
}
|
||||
new_efficacy_indicator = objects.EfficacyIndicator(
|
||||
context, **efficacy_indicator_dict)
|
||||
new_efficacy_indicator.create(context)
|
||||
new_efficacy_indicator.create()
|
||||
|
||||
efficacy_indicators.append(new_efficacy_indicator)
|
||||
return efficacy_indicators
|
||||
@@ -156,7 +156,7 @@ class DefaultPlanner(base.BasePlanner):
|
||||
_action.get("action_type"))
|
||||
|
||||
new_action = objects.Action(context, **_action)
|
||||
new_action.create(context)
|
||||
new_action.create()
|
||||
new_action.save()
|
||||
|
||||
if parent_action:
|
||||
|
@@ -23,8 +23,6 @@ from watcher.common import context
|
||||
from watcher.decision_engine.loading import default
|
||||
from watcher.decision_engine.scoring import scoring_factory
|
||||
from watcher import objects
|
||||
from watcher.objects import action_plan as apobjects
|
||||
from watcher.objects import audit as auditobjects
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
@@ -338,13 +336,13 @@ class Syncer(object):
|
||||
for audit in stale_audits:
|
||||
if audit.id not in self.stale_audits_map:
|
||||
audit.strategy_id = synced_strategy.id
|
||||
audit.state = auditobjects.State.CANCELLED
|
||||
audit.state = objects.audit.State.CANCELLED
|
||||
self.stale_audits_map[audit.id] = audit
|
||||
else:
|
||||
self.stale_audits_map[
|
||||
audit.id].strategy_id = synced_strategy.id
|
||||
self.stale_audits_map[
|
||||
audit.id].state = auditobjects.State.CANCELLED
|
||||
audit.id].state = objects.audit.State.CANCELLED
|
||||
|
||||
def _find_stale_action_plans_due_to_strategy(self):
|
||||
for strategy_id, synced_strategy in self.strategy_mapping.items():
|
||||
@@ -356,13 +354,14 @@ class Syncer(object):
|
||||
for action_plan in stale_action_plans:
|
||||
if action_plan.id not in self.stale_action_plans_map:
|
||||
action_plan.strategy_id = synced_strategy.id
|
||||
action_plan.state = apobjects.State.CANCELLED
|
||||
action_plan.state = objects.action_plan.State.CANCELLED
|
||||
self.stale_action_plans_map[action_plan.id] = action_plan
|
||||
else:
|
||||
self.stale_action_plans_map[
|
||||
action_plan.id].strategy_id = synced_strategy.id
|
||||
self.stale_action_plans_map[
|
||||
action_plan.id].state = apobjects.State.CANCELLED
|
||||
action_plan.id].state = (
|
||||
objects.action_plan.State.CANCELLED)
|
||||
|
||||
def _find_stale_action_plans_due_to_audit(self):
|
||||
for audit_id, synced_audit in self.stale_audits_map.items():
|
||||
@@ -374,13 +373,14 @@ class Syncer(object):
|
||||
for action_plan in stale_action_plans:
|
||||
if action_plan.id not in self.stale_action_plans_map:
|
||||
action_plan.audit_id = synced_audit.id
|
||||
action_plan.state = apobjects.State.CANCELLED
|
||||
action_plan.state = objects.action_plan.State.CANCELLED
|
||||
self.stale_action_plans_map[action_plan.id] = action_plan
|
||||
else:
|
||||
self.stale_action_plans_map[
|
||||
action_plan.id].audit_id = synced_audit.id
|
||||
self.stale_action_plans_map[
|
||||
action_plan.id].state = apobjects.State.CANCELLED
|
||||
action_plan.id].state = (
|
||||
objects.action_plan.State.CANCELLED)
|
||||
|
||||
def _soft_delete_removed_goals(self):
|
||||
removed_goals = [
|
||||
@@ -402,11 +402,11 @@ class Syncer(object):
|
||||
_LW("Audit '%(audit)s' references a "
|
||||
"goal that does not exist"), audit=audit.uuid)
|
||||
if audit.id not in self.stale_audits_map:
|
||||
audit.state = auditobjects.State.CANCELLED
|
||||
audit.state = objects.audit.State.CANCELLED
|
||||
self.stale_audits_map[audit.id] = audit
|
||||
else:
|
||||
self.stale_audits_map[
|
||||
audit.id].state = auditobjects.State.CANCELLED
|
||||
audit.id].state = objects.audit.State.CANCELLED
|
||||
|
||||
def _soft_delete_removed_strategies(self):
|
||||
removed_strategies = [
|
||||
@@ -437,11 +437,11 @@ class Syncer(object):
|
||||
_LW("Audit '%(audit)s' references a "
|
||||
"strategy that does not exist"), audit=audit.uuid)
|
||||
if audit.id not in self.stale_audits_map:
|
||||
audit.state = auditobjects.State.CANCELLED
|
||||
audit.state = objects.audit.State.CANCELLED
|
||||
self.stale_audits_map[audit.id] = audit
|
||||
else:
|
||||
self.stale_audits_map[
|
||||
audit.id].state = auditobjects.State.CANCELLED
|
||||
audit.id].state = objects.audit.State.CANCELLED
|
||||
|
||||
stale_action_plans = objects.ActionPlan.list(
|
||||
self.ctx, filters=filters)
|
||||
@@ -451,11 +451,12 @@ class Syncer(object):
|
||||
"strategy that does not exist"),
|
||||
action_plan=action_plan.uuid)
|
||||
if action_plan.id not in self.stale_action_plans_map:
|
||||
action_plan.state = apobjects.State.CANCELLED
|
||||
action_plan.state = objects.action_plan.State.CANCELLED
|
||||
self.stale_action_plans_map[action_plan.id] = action_plan
|
||||
else:
|
||||
self.stale_action_plans_map[
|
||||
action_plan.id].state = apobjects.State.CANCELLED
|
||||
action_plan.id].state = (
|
||||
objects.action_plan.State.CANCELLED)
|
||||
|
||||
def _soft_delete_removed_scoringengines(self):
|
||||
removed_se = [
|
||||
|
@@ -1,37 +1,35 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# Copyright 2013 IBM Corp.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from watcher.objects import action
|
||||
from watcher.objects import action_plan
|
||||
from watcher.objects import audit
|
||||
from watcher.objects import audit_template
|
||||
from watcher.objects import efficacy_indicator
|
||||
from watcher.objects import goal
|
||||
from watcher.objects import scoring_engine
|
||||
from watcher.objects import service
|
||||
from watcher.objects import strategy
|
||||
# NOTE(comstud): You may scratch your head as you see code that imports
|
||||
# this module and then accesses attributes for objects such as Node,
|
||||
# etc, yet you do not see these attributes in here. Never fear, there is
|
||||
# a little bit of magic. When objects are registered, an attribute is set
|
||||
# on this module automatically, pointing to the newest/latest version of
|
||||
# the object.
|
||||
|
||||
Audit = audit.Audit
|
||||
AuditTemplate = audit_template.AuditTemplate
|
||||
Action = action.Action
|
||||
ActionPlan = action_plan.ActionPlan
|
||||
Goal = goal.Goal
|
||||
ScoringEngine = scoring_engine.ScoringEngine
|
||||
Strategy = strategy.Strategy
|
||||
EfficacyIndicator = efficacy_indicator.EfficacyIndicator
|
||||
Service = service.Service
|
||||
|
||||
__all__ = ("Audit", "AuditTemplate", "Action", "ActionPlan", "Goal",
|
||||
"ScoringEngine", "Strategy", "EfficacyIndicator", "Service")
|
||||
def register_all():
|
||||
# NOTE(danms): You must make sure your object gets imported in this
|
||||
# function in order for it to be registered by services that may
|
||||
# need to receive it via RPC.
|
||||
__import__('watcher.objects.goal')
|
||||
__import__('watcher.objects.strategy')
|
||||
__import__('watcher.objects.audit_template')
|
||||
__import__('watcher.objects.audit')
|
||||
__import__('watcher.objects.action_plan')
|
||||
__import__('watcher.objects.action')
|
||||
__import__('watcher.objects.efficacy_indicator')
|
||||
__import__('watcher.objects.scoring_engine')
|
||||
__import__('watcher.objects.service')
|
||||
|
@@ -14,12 +14,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api as dbapi
|
||||
from watcher.objects import base
|
||||
from watcher.objects import utils as obj_utils
|
||||
from watcher.objects import fields as wfields
|
||||
|
||||
|
||||
class State(object):
|
||||
@@ -31,38 +30,26 @@ class State(object):
|
||||
CANCELLED = 'CANCELLED'
|
||||
|
||||
|
||||
class Action(base.WatcherObject):
|
||||
@base.WatcherObjectRegistry.register
|
||||
class Action(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': int,
|
||||
'uuid': obj_utils.str_or_none,
|
||||
'action_plan_id': obj_utils.int_or_none,
|
||||
'action_type': obj_utils.str_or_none,
|
||||
'input_parameters': obj_utils.dict_or_none,
|
||||
'state': obj_utils.str_or_none,
|
||||
'next': obj_utils.int_or_none,
|
||||
'id': wfields.IntegerField(),
|
||||
'uuid': wfields.UUIDField(),
|
||||
'action_plan_id': wfields.IntegerField(nullable=True),
|
||||
'action_type': wfields.StringField(nullable=True),
|
||||
'input_parameters': wfields.DictField(nullable=True),
|
||||
'state': wfields.StringField(nullable=True),
|
||||
'next': wfields.IntegerField(nullable=True),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(action, db_action):
|
||||
"""Converts a database entity to a formal object."""
|
||||
for field in action.fields:
|
||||
action[field] = db_action[field]
|
||||
|
||||
action.obj_reset_changes()
|
||||
return action
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object_list(db_objects, cls, context):
|
||||
"""Converts a list of database entities to a list of formal objects."""
|
||||
return \
|
||||
[Action._from_db_object(cls(context), obj) for obj in db_objects]
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, action_id):
|
||||
"""Find a action based on its id or uuid and return a Action object.
|
||||
|
||||
@@ -76,7 +63,7 @@ class Action(base.WatcherObject):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=action_id)
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_id(cls, context, action_id):
|
||||
"""Find a action based on its integer id and return a Action object.
|
||||
|
||||
@@ -87,7 +74,7 @@ class Action(base.WatcherObject):
|
||||
action = Action._from_db_object(cls(context), db_action)
|
||||
return action
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find a action based on uuid and return a :class:`Action` object.
|
||||
|
||||
@@ -99,7 +86,7 @@ class Action(base.WatcherObject):
|
||||
action = Action._from_db_object(cls(context), db_action)
|
||||
return action
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def list(cls, context, limit=None, marker=None, filters=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of Action objects.
|
||||
@@ -111,7 +98,6 @@ class Action(base.WatcherObject):
|
||||
:param sort_key: column to sort results by.
|
||||
:param sort_dir: direction to sort. "asc" or "desc".
|
||||
:returns: a list of :class:`Action` object.
|
||||
|
||||
"""
|
||||
db_actions = cls.dbapi.get_action_list(context,
|
||||
limit=limit,
|
||||
@@ -119,84 +105,48 @@ class Action(base.WatcherObject):
|
||||
filters=filters,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return Action._from_db_object_list(db_actions, cls, context)
|
||||
|
||||
def create(self, context=None):
|
||||
"""Create a Action record in the DB.
|
||||
return [cls._from_db_object(cls(context), obj)
|
||||
for obj in db_actions]
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Action(context)
|
||||
|
||||
"""
|
||||
@base.remotable
|
||||
def create(self):
|
||||
"""Create a Action record in the DB"""
|
||||
values = self.obj_get_changes()
|
||||
db_action = self.dbapi.create_action(values)
|
||||
self._from_db_object(self, db_action)
|
||||
|
||||
def destroy(self, context=None):
|
||||
"""Delete the Action from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Action(context)
|
||||
"""
|
||||
def destroy(self):
|
||||
"""Delete the Action from the DB"""
|
||||
self.dbapi.destroy_action(self.uuid)
|
||||
self.obj_reset_changes()
|
||||
|
||||
def save(self, context=None):
|
||||
@base.remotable
|
||||
def save(self):
|
||||
"""Save updates to this Action.
|
||||
|
||||
Updates will be made column by column based on the result
|
||||
of self.what_changed().
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Action(context)
|
||||
"""
|
||||
updates = self.obj_get_changes()
|
||||
self.dbapi.update_action(self.uuid, updates)
|
||||
|
||||
self.obj_reset_changes()
|
||||
|
||||
def refresh(self, context=None):
|
||||
@base.remotable
|
||||
def refresh(self):
|
||||
"""Loads updates for this Action.
|
||||
|
||||
Loads a action with the same uuid from the database and
|
||||
checks for updated attributes. Updates are applied from
|
||||
the loaded action column by column, if there are any updates.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Action(context)
|
||||
"""
|
||||
current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
|
||||
for field in self.fields:
|
||||
if (hasattr(self, base.get_attrname(field)) and
|
||||
self[field] != current[field]):
|
||||
self[field] = current[field]
|
||||
self.obj_refresh(current)
|
||||
|
||||
def soft_delete(self, context=None):
|
||||
"""soft Delete the Audit from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Audit(context)
|
||||
"""
|
||||
@base.remotable
|
||||
def soft_delete(self):
|
||||
"""Soft Delete the Audit from the DB"""
|
||||
self.dbapi.soft_delete_action(self.uuid)
|
||||
self.state = "DELETED"
|
||||
self.state = State.DELETED
|
||||
self.save()
|
||||
|
@@ -45,8 +45,9 @@ composed of two types of Action Item(s):
|
||||
|
||||
An :ref:`Action Plan <action_plan_definition>` may be described using
|
||||
standard workflow model description formats such as
|
||||
`Business Process Model and Notation 2.0 (BPMN 2.0) <http://www.omg.org/spec/BPMN/2.0/>`_
|
||||
or `Unified Modeling Language (UML) <http://www.uml.org/>`_.
|
||||
`Business Process Model and Notation 2.0 (BPMN 2.0)
|
||||
<http://www.omg.org/spec/BPMN/2.0/>`_ or `Unified Modeling Language (UML)
|
||||
<http://www.uml.org/>`_.
|
||||
|
||||
An :ref:`Action Plan <action_plan_definition>` has a life-cycle and its current
|
||||
state may be one of the following:
|
||||
@@ -66,7 +67,7 @@ state may be one of the following:
|
||||
- **CANCELLED** : the :ref:`Action Plan <action_plan_definition>` was in
|
||||
**PENDING** or **ONGOING** state and was cancelled by the
|
||||
:ref:`Administrator <administrator_definition>`
|
||||
""" # noqa
|
||||
"""
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
@@ -74,7 +75,7 @@ from watcher.db import api as dbapi
|
||||
from watcher.objects import action as action_objects
|
||||
from watcher.objects import base
|
||||
from watcher.objects import efficacy_indicator as indicator_objects
|
||||
from watcher.objects import utils as obj_utils
|
||||
from watcher.objects import fields as wfields
|
||||
|
||||
|
||||
class State(object):
|
||||
@@ -87,38 +88,25 @@ class State(object):
|
||||
CANCELLED = 'CANCELLED'
|
||||
|
||||
|
||||
class ActionPlan(base.WatcherObject):
|
||||
@base.WatcherObjectRegistry.register
|
||||
class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': int,
|
||||
'uuid': obj_utils.str_or_none,
|
||||
'audit_id': obj_utils.int_or_none,
|
||||
'strategy_id': obj_utils.int_or_none,
|
||||
'first_action_id': obj_utils.int_or_none,
|
||||
'state': obj_utils.str_or_none,
|
||||
'global_efficacy': obj_utils.dict_or_none,
|
||||
'id': wfields.IntegerField(),
|
||||
'uuid': wfields.UUIDField(),
|
||||
'audit_id': wfields.IntegerField(nullable=True),
|
||||
'strategy_id': wfields.IntegerField(),
|
||||
'first_action_id': wfields.IntegerField(nullable=True),
|
||||
'state': wfields.StringField(nullable=True),
|
||||
'global_efficacy': wfields.FlexibleDictField(nullable=True),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(action_plan, db_action_plan):
|
||||
"""Converts a database entity to a formal object."""
|
||||
for field in action_plan.fields:
|
||||
action_plan[field] = db_action_plan[field]
|
||||
|
||||
action_plan.obj_reset_changes()
|
||||
return action_plan
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object_list(db_objects, cls, context):
|
||||
"""Converts a list of database entities to a list of formal objects."""
|
||||
return [ActionPlan._from_db_object(
|
||||
cls(context), obj) for obj in db_objects]
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, action_plan_id):
|
||||
"""Find a action_plan based on its id or uuid and return a Action object.
|
||||
|
||||
@@ -132,7 +120,7 @@ class ActionPlan(base.WatcherObject):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=action_plan_id)
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_id(cls, context, action_plan_id):
|
||||
"""Find a action_plan based on its integer id and return a Action object.
|
||||
|
||||
@@ -145,7 +133,7 @@ class ActionPlan(base.WatcherObject):
|
||||
cls(context), db_action_plan)
|
||||
return action_plan
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find a action_plan based on uuid and return a :class:`Action` object.
|
||||
|
||||
@@ -157,7 +145,7 @@ class ActionPlan(base.WatcherObject):
|
||||
action_plan = ActionPlan._from_db_object(cls(context), db_action_plan)
|
||||
return action_plan
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def list(cls, context, limit=None, marker=None, filters=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of Action objects.
|
||||
@@ -177,33 +165,20 @@ class ActionPlan(base.WatcherObject):
|
||||
filters=filters,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return ActionPlan._from_db_object_list(db_action_plans, cls, context)
|
||||
|
||||
def create(self, context=None):
|
||||
"""Create a Action record in the DB.
|
||||
return [cls._from_db_object(cls(context), obj)
|
||||
for obj in db_action_plans]
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Action(context)
|
||||
|
||||
"""
|
||||
@base.remotable
|
||||
def create(self):
|
||||
"""Create a Action record in the DB"""
|
||||
values = self.obj_get_changes()
|
||||
db_action_plan = self.dbapi.create_action_plan(values)
|
||||
self._from_db_object(self, db_action_plan)
|
||||
|
||||
def destroy(self, context=None):
|
||||
"""Delete the action plan from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Action(context)
|
||||
"""
|
||||
@base.remotable
|
||||
def destroy(self):
|
||||
"""Delete the action plan from the DB"""
|
||||
related_efficacy_indicators = indicator_objects.EfficacyIndicator.list(
|
||||
context=self._context,
|
||||
filters={"action_plan_uuid": self.uuid})
|
||||
@@ -215,54 +190,32 @@ class ActionPlan(base.WatcherObject):
|
||||
self.dbapi.destroy_action_plan(self.uuid)
|
||||
self.obj_reset_changes()
|
||||
|
||||
def save(self, context=None):
|
||||
@base.remotable
|
||||
def save(self):
|
||||
"""Save updates to this Action plan.
|
||||
|
||||
Updates will be made column by column based on the result
|
||||
of self.what_changed().
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Action(context)
|
||||
"""
|
||||
updates = self.obj_get_changes()
|
||||
self.dbapi.update_action_plan(self.uuid, updates)
|
||||
|
||||
self.obj_reset_changes()
|
||||
|
||||
def refresh(self, context=None):
|
||||
@base.remotable
|
||||
def refresh(self):
|
||||
"""Loads updates for this Action plan.
|
||||
|
||||
Loads a action_plan with the same uuid from the database and
|
||||
checks for updated attributes. Updates are applied from
|
||||
the loaded action_plan column by column, if there are any updates.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Action(context)
|
||||
"""
|
||||
current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
|
||||
for field in self.fields:
|
||||
if (hasattr(self, base.get_attrname(field)) and
|
||||
self[field] != current[field]):
|
||||
self[field] = current[field]
|
||||
self.obj_refresh(current)
|
||||
|
||||
def soft_delete(self, context=None):
|
||||
"""Soft Delete the Action plan from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Audit(context)
|
||||
"""
|
||||
@base.remotable
|
||||
def soft_delete(self):
|
||||
"""Soft Delete the Action plan from the DB"""
|
||||
related_actions = action_objects.Action.list(
|
||||
context=self._context,
|
||||
filters={"action_plan_uuid": self.uuid})
|
||||
|
@@ -54,7 +54,7 @@ from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api as dbapi
|
||||
from watcher.objects import base
|
||||
from watcher.objects import utils as obj_utils
|
||||
from watcher.objects import fields as wfields
|
||||
|
||||
|
||||
class State(object):
|
||||
@@ -72,40 +72,27 @@ class AuditType(enum.Enum):
|
||||
CONTINUOUS = 'CONTINUOUS'
|
||||
|
||||
|
||||
class Audit(base.WatcherObject):
|
||||
@base.WatcherObjectRegistry.register
|
||||
class Audit(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': int,
|
||||
'uuid': obj_utils.str_or_none,
|
||||
'audit_type': obj_utils.str_or_none,
|
||||
'state': obj_utils.str_or_none,
|
||||
'parameters': obj_utils.dict_or_none,
|
||||
'interval': obj_utils.int_or_none,
|
||||
'goal_id': obj_utils.int_or_none,
|
||||
'strategy_id': obj_utils.int_or_none,
|
||||
'scope': obj_utils.list_or_none,
|
||||
'id': wfields.IntegerField(),
|
||||
'uuid': wfields.UUIDField(),
|
||||
'audit_type': wfields.StringField(),
|
||||
'state': wfields.StringField(),
|
||||
'parameters': wfields.FlexibleDictField(nullable=True),
|
||||
'interval': wfields.IntegerField(nullable=True),
|
||||
'scope': wfields.FlexibleListOfDictField(nullable=True),
|
||||
'goal_id': wfields.IntegerField(),
|
||||
'strategy_id': wfields.IntegerField(nullable=True),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(audit, db_audit):
|
||||
"""Converts a database entity to a formal object."""
|
||||
for field in audit.fields:
|
||||
audit[field] = db_audit[field]
|
||||
|
||||
audit.obj_reset_changes()
|
||||
return audit
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object_list(db_objects, cls, context):
|
||||
"""Converts a list of database entities to a list of formal objects."""
|
||||
return \
|
||||
[Audit._from_db_object(cls(context), obj) for obj in db_objects]
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, audit_id):
|
||||
"""Find a audit based on its id or uuid and return a Audit object.
|
||||
|
||||
@@ -125,7 +112,7 @@ class Audit(base.WatcherObject):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=audit_id)
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_id(cls, context, audit_id):
|
||||
"""Find a audit based on its integer id and return a Audit object.
|
||||
|
||||
@@ -142,7 +129,7 @@ class Audit(base.WatcherObject):
|
||||
audit = Audit._from_db_object(cls(context), db_audit)
|
||||
return audit
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find a audit based on uuid and return a :class:`Audit` object.
|
||||
|
||||
@@ -160,7 +147,7 @@ class Audit(base.WatcherObject):
|
||||
audit = Audit._from_db_object(cls(context), db_audit)
|
||||
return audit
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def list(cls, context, limit=None, marker=None, filters=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of Audit objects.
|
||||
@@ -185,84 +172,46 @@ class Audit(base.WatcherObject):
|
||||
filters=filters,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return Audit._from_db_object_list(db_audits, cls, context)
|
||||
return [cls._from_db_object(cls(context), obj) for obj in db_audits]
|
||||
|
||||
def create(self, context=None):
|
||||
"""Create a Audit record in the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Audit(context)
|
||||
|
||||
"""
|
||||
@base.remotable
|
||||
def create(self):
|
||||
"""Create a Audit record in the DB."""
|
||||
values = self.obj_get_changes()
|
||||
db_audit = self.dbapi.create_audit(values)
|
||||
self._from_db_object(self, db_audit)
|
||||
|
||||
def destroy(self, context=None):
|
||||
"""Delete the Audit from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Audit(context)
|
||||
"""
|
||||
def destroy(self):
|
||||
"""Delete the Audit from the DB."""
|
||||
self.dbapi.destroy_audit(self.uuid)
|
||||
self.obj_reset_changes()
|
||||
|
||||
def save(self, context=None):
|
||||
@base.remotable
|
||||
def save(self):
|
||||
"""Save updates to this Audit.
|
||||
|
||||
Updates will be made column by column based on the result
|
||||
of self.what_changed().
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Audit(context)
|
||||
"""
|
||||
updates = self.obj_get_changes()
|
||||
self.dbapi.update_audit(self.uuid, updates)
|
||||
|
||||
self.obj_reset_changes()
|
||||
|
||||
def refresh(self, context=None):
|
||||
@base.remotable
|
||||
def refresh(self):
|
||||
"""Loads updates for this Audit.
|
||||
|
||||
Loads a audit with the same uuid from the database and
|
||||
checks for updated attributes. Updates are applied from
|
||||
the loaded audit column by column, if there are any updates.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Audit(context)
|
||||
"""
|
||||
current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
|
||||
for field in self.fields:
|
||||
if (hasattr(self, base.get_attrname(field)) and
|
||||
self[field] != current[field]):
|
||||
self[field] = current[field]
|
||||
self.obj_refresh(current)
|
||||
|
||||
def soft_delete(self, context=None):
|
||||
"""soft Delete the Audit from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Audit(context)
|
||||
"""
|
||||
@base.remotable
|
||||
def soft_delete(self):
|
||||
"""Soft Delete the Audit from the DB."""
|
||||
self.dbapi.soft_delete_audit(self.uuid)
|
||||
self.state = "DELETED"
|
||||
self.state = State.DELETED
|
||||
self.save()
|
||||
|
@@ -51,41 +51,28 @@ from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api as dbapi
|
||||
from watcher.objects import base
|
||||
from watcher.objects import utils as obj_utils
|
||||
from watcher.objects import fields as wfields
|
||||
|
||||
|
||||
class AuditTemplate(base.WatcherObject):
|
||||
@base.WatcherObjectRegistry.register
|
||||
class AuditTemplate(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': int,
|
||||
'uuid': obj_utils.str_or_none,
|
||||
'name': obj_utils.str_or_none,
|
||||
'description': obj_utils.str_or_none,
|
||||
'goal_id': obj_utils.int_or_none,
|
||||
'strategy_id': obj_utils.int_or_none,
|
||||
'scope': obj_utils.list_or_none,
|
||||
'id': wfields.IntegerField(),
|
||||
'uuid': wfields.UUIDField(),
|
||||
'name': wfields.StringField(),
|
||||
'description': wfields.StringField(nullable=True),
|
||||
'scope': wfields.FlexibleListOfDictField(nullable=True),
|
||||
'goal_id': wfields.IntegerField(),
|
||||
'strategy_id': wfields.IntegerField(nullable=True),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(audit_template, db_audit_template):
|
||||
"""Converts a database entity to a formal object."""
|
||||
for field in audit_template.fields:
|
||||
audit_template[field] = db_audit_template[field]
|
||||
|
||||
audit_template.obj_reset_changes()
|
||||
return audit_template
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object_list(db_objects, cls, context):
|
||||
"""Converts a list of database entities to a list of formal objects."""
|
||||
return [AuditTemplate._from_db_object(cls(context), obj)
|
||||
for obj in db_objects]
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, audit_template_id):
|
||||
"""Find an audit template based on its id or uuid
|
||||
|
||||
@@ -98,7 +85,6 @@ class AuditTemplate(base.WatcherObject):
|
||||
:param audit_template_id: the id *or* uuid of a audit_template.
|
||||
:returns: a :class:`AuditTemplate` object.
|
||||
"""
|
||||
|
||||
if utils.is_int_like(audit_template_id):
|
||||
return cls.get_by_id(context, audit_template_id)
|
||||
elif utils.is_uuid_like(audit_template_id):
|
||||
@@ -106,7 +92,7 @@ class AuditTemplate(base.WatcherObject):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=audit_template_id)
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_id(cls, context, audit_template_id):
|
||||
"""Find an audit template based on its integer id
|
||||
|
||||
@@ -119,7 +105,6 @@ class AuditTemplate(base.WatcherObject):
|
||||
:param audit_template_id: the id of a audit_template.
|
||||
:returns: a :class:`AuditTemplate` object.
|
||||
"""
|
||||
|
||||
db_audit_template = cls.dbapi.get_audit_template_by_id(
|
||||
context,
|
||||
audit_template_id)
|
||||
@@ -127,7 +112,7 @@ class AuditTemplate(base.WatcherObject):
|
||||
db_audit_template)
|
||||
return audit_template
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find an audit template based on uuid
|
||||
|
||||
@@ -140,13 +125,12 @@ class AuditTemplate(base.WatcherObject):
|
||||
:param uuid: the uuid of a audit_template.
|
||||
:returns: a :class:`AuditTemplate` object.
|
||||
"""
|
||||
|
||||
db_audit_template = cls.dbapi.get_audit_template_by_uuid(context, uuid)
|
||||
audit_template = AuditTemplate._from_db_object(cls(context),
|
||||
db_audit_template)
|
||||
return audit_template
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_name(cls, context, name):
|
||||
"""Find an audit template based on name
|
||||
|
||||
@@ -154,13 +138,12 @@ class AuditTemplate(base.WatcherObject):
|
||||
:param context: Security context
|
||||
:returns: a :class:`AuditTemplate` object.
|
||||
"""
|
||||
|
||||
db_audit_template = cls.dbapi.get_audit_template_by_name(context, name)
|
||||
audit_template = AuditTemplate._from_db_object(cls(context),
|
||||
db_audit_template)
|
||||
return audit_template
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def list(cls, context, filters=None, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of :class:`AuditTemplate` objects.
|
||||
@@ -178,7 +161,6 @@ class AuditTemplate(base.WatcherObject):
|
||||
:param sort_dir: direction to sort. "asc" or "desc".
|
||||
:returns: a list of :class:`AuditTemplate` object.
|
||||
"""
|
||||
|
||||
db_audit_templates = cls.dbapi.get_audit_template_list(
|
||||
context,
|
||||
filters=filters,
|
||||
@@ -186,87 +168,46 @@ class AuditTemplate(base.WatcherObject):
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return AuditTemplate._from_db_object_list(db_audit_templates,
|
||||
cls, context)
|
||||
|
||||
def create(self, context=None):
|
||||
"""Create a :class:`AuditTemplate` record in the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: AuditTemplate(context)
|
||||
"""
|
||||
return [cls._from_db_object(cls(context), obj)
|
||||
for obj in db_audit_templates]
|
||||
|
||||
@base.remotable
|
||||
def create(self):
|
||||
"""Create a :class:`AuditTemplate` record in the DB"""
|
||||
values = self.obj_get_changes()
|
||||
db_audit_template = self.dbapi.create_audit_template(values)
|
||||
self._from_db_object(self, db_audit_template)
|
||||
|
||||
def destroy(self, context=None):
|
||||
"""Delete the :class:`AuditTemplate` from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: AuditTemplate(context)
|
||||
"""
|
||||
|
||||
def destroy(self):
|
||||
"""Delete the :class:`AuditTemplate` from the DB"""
|
||||
self.dbapi.destroy_audit_template(self.uuid)
|
||||
self.obj_reset_changes()
|
||||
|
||||
def save(self, context=None):
|
||||
@base.remotable
|
||||
def save(self):
|
||||
"""Save updates to this :class:`AuditTemplate`.
|
||||
|
||||
Updates will be made column by column based on the result
|
||||
of self.what_changed().
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: AuditTemplate(context)
|
||||
"""
|
||||
|
||||
updates = self.obj_get_changes()
|
||||
self.dbapi.update_audit_template(self.uuid, updates)
|
||||
|
||||
self.obj_reset_changes()
|
||||
|
||||
def refresh(self, context=None):
|
||||
@base.remotable
|
||||
def refresh(self):
|
||||
"""Loads updates for this :class:`AuditTemplate`.
|
||||
|
||||
Loads a audit_template with the same uuid from the database and
|
||||
checks for updated attributes. Updates are applied from
|
||||
the loaded audit_template column by column, if there are any updates.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: AuditTemplate(context)
|
||||
"""
|
||||
|
||||
current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
|
||||
for field in self.fields:
|
||||
if (hasattr(self, base.get_attrname(field)) and
|
||||
self[field] != current[field]):
|
||||
self[field] = current[field]
|
||||
|
||||
def soft_delete(self, context=None):
|
||||
"""soft Delete the :class:`AuditTemplate` from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: AuditTemplate(context)
|
||||
"""
|
||||
self.obj_refresh(current)
|
||||
|
||||
@base.remotable
|
||||
def soft_delete(self):
|
||||
"""Soft Delete the :class:`AuditTemplate` from the DB"""
|
||||
self.dbapi.soft_delete_audit_template(self.uuid)
|
||||
|
@@ -14,109 +14,62 @@
|
||||
|
||||
"""Watcher common internal object model"""
|
||||
|
||||
import collections
|
||||
import copy
|
||||
|
||||
from oslo_log import log as logging
|
||||
import oslo_messaging as messaging
|
||||
from oslo_utils import versionutils
|
||||
import six
|
||||
from oslo_versionedobjects import base as ovo_base
|
||||
from oslo_versionedobjects import fields as ovo_fields
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher._i18n import _LE
|
||||
from watcher.common import exception
|
||||
from watcher.objects import utils as obj_utils
|
||||
from watcher import objects
|
||||
|
||||
|
||||
LOG = logging.getLogger('object')
|
||||
|
||||
|
||||
class NotSpecifiedSentinel(object):
|
||||
pass
|
||||
remotable_classmethod = ovo_base.remotable_classmethod
|
||||
remotable = ovo_base.remotable
|
||||
|
||||
|
||||
def get_attrname(name):
|
||||
"""Return the mangled name of the attribute's underlying storage."""
|
||||
return '_%s' % name
|
||||
# FIXME(danms): This is just until we use o.vo's class properties
|
||||
# and object base.
|
||||
return '_obj_' + name
|
||||
|
||||
|
||||
def make_class_properties(cls):
|
||||
# NOTE(danms/comstud): Inherit fields from super classes.
|
||||
# mro() returns the current class first and returns 'object' last, so
|
||||
# those can be skipped. Also be careful to not overwrite any fields
|
||||
# that already exist. And make sure each cls has its own copy of
|
||||
# fields and that it is not sharing the dict with a super class.
|
||||
cls.fields = dict(cls.fields)
|
||||
for supercls in cls.mro()[1:-1]:
|
||||
if not hasattr(supercls, 'fields'):
|
||||
continue
|
||||
for name, field in supercls.fields.items():
|
||||
if name not in cls.fields:
|
||||
cls.fields[name] = field
|
||||
for name, typefn in cls.fields.items():
|
||||
class WatcherObjectRegistry(ovo_base.VersionedObjectRegistry):
|
||||
notification_classes = []
|
||||
|
||||
def getter(self, name=name):
|
||||
attrname = get_attrname(name)
|
||||
if not hasattr(self, attrname):
|
||||
self.obj_load_attr(name)
|
||||
return getattr(self, attrname)
|
||||
|
||||
def setter(self, value, name=name, typefn=typefn):
|
||||
self._changed_fields.add(name)
|
||||
try:
|
||||
return setattr(self, get_attrname(name), typefn(value))
|
||||
except Exception:
|
||||
attr = "%s.%s" % (self.obj_name(), name)
|
||||
LOG.exception(_LE('Error setting %(attr)s'),
|
||||
{'attr': attr})
|
||||
raise
|
||||
|
||||
setattr(cls, name, property(getter, setter))
|
||||
|
||||
|
||||
class WatcherObjectMetaclass(type):
|
||||
"""Metaclass that allows tracking of object classes."""
|
||||
|
||||
# NOTE(danms): This is what controls whether object operations are
|
||||
# remoted. If this is not None, use it to remote things over RPC.
|
||||
indirection_api = None
|
||||
|
||||
def __init__(cls, names, bases, dict_):
|
||||
if not hasattr(cls, '_obj_classes'):
|
||||
# This will be set in the 'WatcherObject' class.
|
||||
cls._obj_classes = collections.defaultdict(list)
|
||||
def registration_hook(self, cls, index):
|
||||
# NOTE(danms): This is called when an object is registered,
|
||||
# and is responsible for maintaining watcher.objects.$OBJECT
|
||||
# as the highest-versioned implementation of a given object.
|
||||
version = versionutils.convert_version_to_tuple(cls.VERSION)
|
||||
if not hasattr(objects, cls.obj_name()):
|
||||
setattr(objects, cls.obj_name(), cls)
|
||||
else:
|
||||
# Add the subclass to WatcherObject._obj_classes
|
||||
make_class_properties(cls)
|
||||
cls._obj_classes[cls.obj_name()].append(cls)
|
||||
cur_version = versionutils.convert_version_to_tuple(
|
||||
getattr(objects, cls.obj_name()).VERSION)
|
||||
if version >= cur_version:
|
||||
setattr(objects, cls.obj_name(), cls)
|
||||
|
||||
@classmethod
|
||||
def register_notification(cls, notification_cls):
|
||||
"""Register a class as notification.
|
||||
|
||||
Use only to register concrete notification or payload classes,
|
||||
do not register base classes intended for inheritance only.
|
||||
"""
|
||||
cls.register_if(False)(notification_cls)
|
||||
cls.notification_classes.append(notification_cls)
|
||||
return notification_cls
|
||||
|
||||
@classmethod
|
||||
def register_notification_objects(cls):
|
||||
"""Register previously decorated notification as normal ovos.
|
||||
|
||||
This is not intended for production use but only for testing and
|
||||
document generation purposes.
|
||||
"""
|
||||
for notification_cls in cls.notification_classes:
|
||||
cls.register(notification_cls)
|
||||
|
||||
|
||||
# Object versioning rules
|
||||
#
|
||||
# Each service has its set of objects, each with a version attached. When
|
||||
# a client attempts to call an object method, the server checks to see if
|
||||
# the version of that object matches (in a compatible way) its object
|
||||
# implementation. If so, cool, and if not, fail.
|
||||
def check_object_version(server, client):
|
||||
try:
|
||||
client_major, _client_minor = client.split('.')
|
||||
server_major, _server_minor = server.split('.')
|
||||
client_minor = int(_client_minor)
|
||||
server_minor = int(_server_minor)
|
||||
except ValueError:
|
||||
raise exception.IncompatibleObjectVersion(
|
||||
_('Invalid version string'))
|
||||
|
||||
if client_major != server_major:
|
||||
raise exception.IncompatibleObjectVersion(
|
||||
dict(client=client_major, server=server_major))
|
||||
if client_minor > server_minor:
|
||||
raise exception.IncompatibleObjectVersion(
|
||||
dict(client=client_minor, server=server_minor))
|
||||
|
||||
|
||||
@six.add_metaclass(WatcherObjectMetaclass)
|
||||
class WatcherObject(object):
|
||||
class WatcherObject(ovo_base.VersionedObject):
|
||||
"""Base class and object factory.
|
||||
|
||||
This forms the base of all objects that can be remoted or instantiated
|
||||
@@ -126,431 +79,59 @@ class WatcherObject(object):
|
||||
as appropriate.
|
||||
"""
|
||||
|
||||
# Version of this object (see rules above check_object_version())
|
||||
VERSION = '1.0'
|
||||
|
||||
# The fields present in this object as key:typefn pairs. For example:
|
||||
#
|
||||
# fields = { 'foo': int,
|
||||
# 'bar': str,
|
||||
# 'baz': lambda x: str(x).ljust(8),
|
||||
# }
|
||||
#
|
||||
# NOTE(danms): The base WatcherObject class' fields will be inherited
|
||||
# by subclasses, but that is a special case. Objects inheriting from
|
||||
# other objects will not receive this merging of fields contents.
|
||||
fields = {
|
||||
'created_at': obj_utils.datetime_or_str_or_none,
|
||||
'updated_at': obj_utils.datetime_or_str_or_none,
|
||||
'deleted_at': obj_utils.datetime_or_str_or_none,
|
||||
}
|
||||
obj_extra_fields = []
|
||||
|
||||
_attr_created_at_from_primitive = obj_utils.dt_deserializer
|
||||
_attr_updated_at_from_primitive = obj_utils.dt_deserializer
|
||||
_attr_created_at_to_primitive = obj_utils.dt_serializer('created_at')
|
||||
_attr_updated_at_to_primitive = obj_utils.dt_serializer('updated_at')
|
||||
_attr_deleted_at_to_primitive = obj_utils.dt_serializer('deleted_at')
|
||||
|
||||
def __init__(self, context, **kwargs):
|
||||
self._changed_fields = set()
|
||||
self._context = context
|
||||
self.update(kwargs)
|
||||
|
||||
@classmethod
|
||||
def obj_name(cls):
|
||||
"""Get canonical object name.
|
||||
|
||||
This object name will be used over the wire for remote hydration.
|
||||
"""
|
||||
return cls.__name__
|
||||
|
||||
@classmethod
|
||||
def obj_class_from_name(cls, objname, objver):
|
||||
"""Returns a class from the registry based on a name and version."""
|
||||
if objname not in cls._obj_classes:
|
||||
LOG.error(_LE('Unable to instantiate unregistered object type '
|
||||
'%(objtype)s'), dict(objtype=objname))
|
||||
raise exception.UnsupportedObjectError(objtype=objname)
|
||||
|
||||
latest = None
|
||||
compatible_match = None
|
||||
for objclass in cls._obj_classes[objname]:
|
||||
if objclass.VERSION == objver:
|
||||
return objclass
|
||||
|
||||
version_bits = tuple([int(x) for x in objclass.VERSION.split(".")])
|
||||
if latest is None:
|
||||
latest = version_bits
|
||||
elif latest < version_bits:
|
||||
latest = version_bits
|
||||
|
||||
if versionutils.is_compatible(objver, objclass.VERSION):
|
||||
compatible_match = objclass
|
||||
|
||||
if compatible_match:
|
||||
return compatible_match
|
||||
|
||||
latest_ver = '%i.%i' % latest
|
||||
raise exception.IncompatibleObjectVersion(objname=objname,
|
||||
objver=objver,
|
||||
supported=latest_ver)
|
||||
|
||||
def _attr_from_primitive(self, attribute, value):
|
||||
"""Attribute deserialization dispatcher.
|
||||
|
||||
This calls self._attr_foo_from_primitive(value) for an attribute
|
||||
foo with value, if it exists, otherwise it assumes the value
|
||||
is suitable for the attribute's setter method.
|
||||
"""
|
||||
handler = '_attr_%s_from_primitive' % attribute
|
||||
if hasattr(self, handler):
|
||||
return getattr(self, handler)(value)
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _obj_from_primitive(cls, context, objver, primitive):
|
||||
self = cls(context)
|
||||
self.VERSION = objver
|
||||
objdata = primitive['watcher_object.data']
|
||||
changes = primitive.get('watcher_object.changes', [])
|
||||
for name in self.fields:
|
||||
if name in objdata:
|
||||
setattr(self, name,
|
||||
self._attr_from_primitive(name, objdata[name]))
|
||||
self._changed_fields = set([x for x in changes if x in self.fields])
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def obj_from_primitive(cls, primitive, context=None):
|
||||
"""Simple base-case hydration.
|
||||
|
||||
This calls self._attr_from_primitive() for each item in fields.
|
||||
"""
|
||||
if primitive['watcher_object.namespace'] != 'watcher':
|
||||
# NOTE(danms): We don't do anything with this now, but it's
|
||||
# there for "the future"
|
||||
raise exception.UnsupportedObjectError(
|
||||
objtype='%s.%s' % (primitive['watcher_object.namespace'],
|
||||
primitive['watcher_object.name']))
|
||||
objname = primitive['watcher_object.name']
|
||||
objver = primitive['watcher_object.version']
|
||||
objclass = cls.obj_class_from_name(objname, objver)
|
||||
return objclass._obj_from_primitive(context, objver, primitive)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
"""Efficiently make a deep copy of this object."""
|
||||
|
||||
# NOTE(danms): A naive deepcopy would copy more than we need,
|
||||
# and since we have knowledge of the volatile bits of the
|
||||
# object, we can be smarter here. Also, nested entities within
|
||||
# some objects may be uncopyable, so we can avoid those sorts
|
||||
# of issues by copying only our field data.
|
||||
|
||||
nobj = self.__class__(self._context)
|
||||
for name in self.fields:
|
||||
if self.obj_attr_is_set(name):
|
||||
nval = copy.deepcopy(getattr(self, name), memo)
|
||||
setattr(nobj, name, nval)
|
||||
nobj._changed_fields = set(self._changed_fields)
|
||||
return nobj
|
||||
|
||||
def obj_clone(self):
|
||||
"""Create a copy."""
|
||||
return copy.deepcopy(self)
|
||||
|
||||
def _attr_to_primitive(self, attribute):
|
||||
"""Attribute serialization dispatcher.
|
||||
|
||||
This calls self._attr_foo_to_primitive() for an attribute foo,
|
||||
if it exists, otherwise it assumes the attribute itself is
|
||||
primitive-enough to be sent over the RPC wire.
|
||||
"""
|
||||
handler = '_attr_%s_to_primitive' % attribute
|
||||
if hasattr(self, handler):
|
||||
return getattr(self, handler)()
|
||||
else:
|
||||
return getattr(self, attribute)
|
||||
|
||||
def obj_to_primitive(self):
|
||||
"""Simple base-case dehydration.
|
||||
|
||||
This calls self._attr_to_primitive() for each item in fields.
|
||||
"""
|
||||
primitive = dict()
|
||||
for name in self.fields:
|
||||
if hasattr(self, get_attrname(name)):
|
||||
primitive[name] = self._attr_to_primitive(name)
|
||||
obj = {'watcher_object.name': self.obj_name(),
|
||||
'watcher_object.namespace': 'watcher',
|
||||
'watcher_object.version': self.VERSION,
|
||||
'watcher_object.data': primitive}
|
||||
if self.obj_what_changed():
|
||||
obj['watcher_object.changes'] = list(self.obj_what_changed())
|
||||
return obj
|
||||
|
||||
def obj_load_attr(self, attrname):
|
||||
"""Load an additional attribute from the real object.
|
||||
|
||||
This should use self._conductor, and cache any data that might
|
||||
be useful for future load operations.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
_("Cannot load '%(attrname)s' in the base class") %
|
||||
{'attrname': attrname})
|
||||
|
||||
def save(self, context):
|
||||
"""Save the changed fields back to the store.
|
||||
|
||||
This is optional for subclasses, but is presented here in the base
|
||||
class for consistency among those that do.
|
||||
"""
|
||||
raise NotImplementedError(_("Cannot save anything in the base class"))
|
||||
|
||||
def obj_get_changes(self):
|
||||
"""Returns a dict of changed fields and their new values."""
|
||||
changes = {}
|
||||
for key in self.obj_what_changed():
|
||||
changes[key] = self._attr_to_primitive(key)
|
||||
return changes
|
||||
|
||||
def obj_what_changed(self):
|
||||
"""Returns a set of fields that have been modified."""
|
||||
return self._changed_fields
|
||||
|
||||
def obj_reset_changes(self, fields=None):
|
||||
"""Reset the list of fields that have been changed.
|
||||
|
||||
Note that this is NOT "revert to previous values"
|
||||
"""
|
||||
if fields:
|
||||
self._changed_fields -= set(fields)
|
||||
else:
|
||||
self._changed_fields.clear()
|
||||
|
||||
def obj_attr_is_set(self, attrname):
|
||||
"""Test object to see if attrname is present.
|
||||
|
||||
Returns True if the named attribute has a value set, or
|
||||
False if not. Raises AttributeError if attrname is not
|
||||
a valid attribute for this object.
|
||||
"""
|
||||
if attrname not in self.obj_fields:
|
||||
raise AttributeError(
|
||||
_("%(objname)s object has no attribute '%(attrname)s'") %
|
||||
{'objname': self.obj_name(), 'attrname': attrname})
|
||||
return hasattr(self, get_attrname(attrname))
|
||||
|
||||
@property
|
||||
def obj_fields(self):
|
||||
return list(self.fields.keys()) + self.obj_extra_fields
|
||||
|
||||
# dictish syntactic sugar
|
||||
def iteritems(self):
|
||||
"""For backwards-compatibility with dict-based objects.
|
||||
|
||||
NOTE(danms): May be removed in the future.
|
||||
"""
|
||||
return self._iteritems()
|
||||
|
||||
# dictish syntactic sugar, internal to pass hacking checks
|
||||
def _iteritems(self):
|
||||
"""For backwards-compatibility with dict-based objects.
|
||||
|
||||
NOTE(danms): May be removed in the future.
|
||||
"""
|
||||
for name in list(self.fields.keys()) + self.obj_extra_fields:
|
||||
if (hasattr(self, get_attrname(name)) or
|
||||
name in self.obj_extra_fields):
|
||||
yield name, getattr(self, name)
|
||||
|
||||
def items(self):
|
||||
return list(self._iteritems())
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""For backwards-compatibility with dict-based objects.
|
||||
|
||||
NOTE(danms): May be removed in the future.
|
||||
"""
|
||||
return getattr(self, name)
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
"""For backwards-compatibility with dict-based objects.
|
||||
|
||||
NOTE(danms): May be removed in the future.
|
||||
"""
|
||||
setattr(self, name, value)
|
||||
|
||||
def __contains__(self, name):
|
||||
"""For backwards-compatibility with dict-based objects.
|
||||
|
||||
NOTE(danms): May be removed in the future.
|
||||
"""
|
||||
return hasattr(self, get_attrname(name))
|
||||
|
||||
def get(self, key, value=NotSpecifiedSentinel):
|
||||
"""For backwards-compatibility with dict-based objects.
|
||||
|
||||
NOTE(danms): May be removed in the future.
|
||||
"""
|
||||
if key not in self.obj_fields:
|
||||
raise AttributeError(
|
||||
_("'%(objclass)s' object has no attribute '%(attrname)s'") %
|
||||
{'objclass': self.__class__, 'attrname': key})
|
||||
if value != NotSpecifiedSentinel and not self.obj_attr_is_set(key):
|
||||
return value
|
||||
else:
|
||||
return self[key]
|
||||
|
||||
def update(self, updates):
|
||||
"""For backwards-compatibility with dict-base objects.
|
||||
|
||||
NOTE(danms): May be removed in the future.
|
||||
"""
|
||||
for key, value in updates.items():
|
||||
self[key] = value
|
||||
OBJ_SERIAL_NAMESPACE = 'watcher_object'
|
||||
OBJ_PROJECT_NAMESPACE = 'watcher'
|
||||
|
||||
def as_dict(self):
|
||||
return dict((k, getattr(self, k))
|
||||
for k in self.fields
|
||||
if hasattr(self, k))
|
||||
return {
|
||||
k: getattr(self, k) for k in self.fields
|
||||
if self.obj_attr_is_set(k)}
|
||||
|
||||
|
||||
class ObjectListBase(object):
|
||||
"""Mixin class for lists of objects.
|
||||
class WatcherObjectDictCompat(ovo_base.VersionedObjectDictCompat):
|
||||
pass
|
||||
|
||||
This mixin class can be added as a base class for an object that
|
||||
is implementing a list of objects. It adds a single field of 'objects',
|
||||
which is the list store, and behaves like a list itself. It supports
|
||||
serialization of the list of objects automatically.
|
||||
|
||||
class WatcherPersistentObject(object):
|
||||
"""Mixin class for Persistent objects.
|
||||
|
||||
This adds the fields that we use in common for all persistent objects.
|
||||
"""
|
||||
fields = {
|
||||
'objects': list,
|
||||
'created_at': ovo_fields.DateTimeField(nullable=True),
|
||||
'updated_at': ovo_fields.DateTimeField(nullable=True),
|
||||
'deleted_at': ovo_fields.DateTimeField(nullable=True),
|
||||
}
|
||||
|
||||
# This is a dictionary of my_version:child_version mappings so that
|
||||
# we can support backleveling our contents based on the version
|
||||
# requested of the list object.
|
||||
child_versions = {}
|
||||
def obj_refresh(self, loaded_object):
|
||||
"""Applies updates for objects that inherit from base.WatcherObject.
|
||||
|
||||
def __iter__(self):
|
||||
"""List iterator interface."""
|
||||
return iter(self.objects)
|
||||
|
||||
def __len__(self):
|
||||
"""List length."""
|
||||
return len(self.objects)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""List index access."""
|
||||
if isinstance(index, slice):
|
||||
new_obj = self.__class__(self._context)
|
||||
new_obj.objects = self.objects[index]
|
||||
# NOTE(danms): We must be mixed in with an WatcherObject!
|
||||
new_obj.obj_reset_changes()
|
||||
return new_obj
|
||||
return self.objects[index]
|
||||
|
||||
def __contains__(self, value):
|
||||
"""List membership test."""
|
||||
return value in self.objects
|
||||
|
||||
def count(self, value):
|
||||
"""List count of value occurrences."""
|
||||
return self.objects.count(value)
|
||||
|
||||
def index(self, value):
|
||||
"""List index of value."""
|
||||
return self.objects.index(value)
|
||||
|
||||
def _attr_objects_to_primitive(self):
|
||||
"""Serialization of object list."""
|
||||
return [x.obj_to_primitive() for x in self.objects]
|
||||
|
||||
def _attr_objects_from_primitive(self, value):
|
||||
"""Deserialization of object list."""
|
||||
objects = []
|
||||
for entity in value:
|
||||
obj = WatcherObject.obj_from_primitive(
|
||||
entity,
|
||||
context=self._context)
|
||||
objects.append(obj)
|
||||
return objects
|
||||
|
||||
def obj_make_compatible(self, primitive, target_version):
|
||||
primitives = primitive['objects']
|
||||
child_target_version = self.child_versions.get(target_version, '1.0')
|
||||
for index, item in enumerate(self.objects):
|
||||
self.objects[index].obj_make_compatible(
|
||||
primitives[index]['watcher_object.data'],
|
||||
child_target_version)
|
||||
primitives[index]['watcher_object.version'] = child_target_version
|
||||
|
||||
def obj_what_changed(self):
|
||||
changes = set(self._changed_fields)
|
||||
for child in self.objects:
|
||||
if child.obj_what_changed():
|
||||
changes.add('objects')
|
||||
return changes
|
||||
|
||||
|
||||
class WatcherObjectSerializer(messaging.NoOpSerializer):
|
||||
"""A WatcherObject-aware Serializer.
|
||||
|
||||
This implements the Oslo Serializer interface and provides the
|
||||
ability to serialize and deserialize WatcherObject entities. Any service
|
||||
that needs to accept or return WatcherObjects as arguments or result values
|
||||
should pass this to its RpcProxy and RpcDispatcher objects.
|
||||
"""
|
||||
|
||||
def _process_iterable(self, context, action_fn, values):
|
||||
"""Process an iterable, taking an action on each value.
|
||||
|
||||
:param:context: Request context
|
||||
:param:action_fn: Action to take on each item in values
|
||||
:param:values: Iterable container of things to take action on
|
||||
:returns: A new container of the same type (except set) with
|
||||
items from values having had action applied.
|
||||
Checks for updated attributes in an object. Updates are applied from
|
||||
the loaded object column by column in comparison with the current
|
||||
object.
|
||||
"""
|
||||
iterable = values.__class__
|
||||
if iterable == set:
|
||||
# NOTE(danms): A set can't have an unhashable value inside, such as
|
||||
# a dict. Convert sets to tuples, which is fine, since we can't
|
||||
# send them over RPC anyway.
|
||||
iterable = tuple
|
||||
return iterable([action_fn(context, value) for value in values])
|
||||
for field in self.fields:
|
||||
if (self.obj_attr_is_set(field) and
|
||||
self[field] != loaded_object[field]):
|
||||
self[field] = loaded_object[field]
|
||||
|
||||
def serialize_entity(self, context, entity):
|
||||
if isinstance(entity, (tuple, list, set)):
|
||||
entity = self._process_iterable(context, self.serialize_entity,
|
||||
entity)
|
||||
elif (hasattr(entity, 'obj_to_primitive') and
|
||||
callable(entity.obj_to_primitive)):
|
||||
entity = entity.obj_to_primitive()
|
||||
return entity
|
||||
@staticmethod
|
||||
def _from_db_object(obj, db_object):
|
||||
"""Converts a database entity to a formal object.
|
||||
|
||||
def deserialize_entity(self, context, entity):
|
||||
if isinstance(entity, dict) and 'watcher_object.name' in entity:
|
||||
entity = WatcherObject.obj_from_primitive(entity, context=context)
|
||||
elif isinstance(entity, (tuple, list, set)):
|
||||
entity = self._process_iterable(context, self.deserialize_entity,
|
||||
entity)
|
||||
return entity
|
||||
:param obj: An object of the class.
|
||||
:param db_object: A DB model of the object
|
||||
:return: The object of the class with the database entity added
|
||||
|
||||
"""
|
||||
|
||||
def obj_to_primitive(obj):
|
||||
"""Recursively turn an object into a python primitive.
|
||||
for field in obj.fields:
|
||||
obj[field] = db_object[field]
|
||||
|
||||
An WatcherObject becomes a dict, and anything that implements
|
||||
ObjectListBase becomes a list.
|
||||
"""
|
||||
|
||||
if isinstance(obj, ObjectListBase):
|
||||
return [obj_to_primitive(x) for x in obj]
|
||||
elif isinstance(obj, WatcherObject):
|
||||
result = {}
|
||||
for key, value in obj.items():
|
||||
result[key] = obj_to_primitive(value)
|
||||
return result
|
||||
else:
|
||||
obj.obj_reset_changes()
|
||||
return obj
|
||||
|
||||
|
||||
class WatcherObjectSerializer(ovo_base.VersionedObjectSerializer):
|
||||
# Base class to use for object hydration
|
||||
OBJ_BASE_CLASS = WatcherObject
|
||||
|
@@ -14,46 +14,32 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api as dbapi
|
||||
from watcher.db import api as db_api
|
||||
from watcher.objects import base
|
||||
from watcher.objects import utils as obj_utils
|
||||
from watcher.objects import fields as wfields
|
||||
|
||||
|
||||
class EfficacyIndicator(base.WatcherObject):
|
||||
@base.WatcherObjectRegistry.register
|
||||
class EfficacyIndicator(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
dbapi = db_api.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': int,
|
||||
'uuid': obj_utils.str_or_none,
|
||||
'action_plan_id': obj_utils.int_or_none,
|
||||
'name': obj_utils.str_or_none,
|
||||
'description': obj_utils.str_or_none,
|
||||
'unit': obj_utils.str_or_none,
|
||||
'value': obj_utils.numeric_or_none,
|
||||
'id': wfields.IntegerField(),
|
||||
'uuid': wfields.UUIDField(),
|
||||
'action_plan_id': wfields.IntegerField(),
|
||||
'name': wfields.StringField(),
|
||||
'description': wfields.StringField(nullable=True),
|
||||
'unit': wfields.StringField(nullable=True),
|
||||
'value': wfields.NumericField(),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(efficacy_indicator, db_efficacy_indicator):
|
||||
"""Converts a database entity to a formal object."""
|
||||
for field in efficacy_indicator.fields:
|
||||
efficacy_indicator[field] = db_efficacy_indicator[field]
|
||||
|
||||
efficacy_indicator.obj_reset_changes()
|
||||
return efficacy_indicator
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object_list(db_objects, cls, context):
|
||||
"""Converts a list of database entities to a list of formal objects."""
|
||||
return [EfficacyIndicator._from_db_object(cls(context), obj)
|
||||
for obj in db_objects]
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, efficacy_indicator_id):
|
||||
"""Find an efficacy indicator object given its ID or UUID
|
||||
|
||||
@@ -67,7 +53,7 @@ class EfficacyIndicator(base.WatcherObject):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=efficacy_indicator_id)
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_id(cls, context, efficacy_indicator_id):
|
||||
"""Find an efficacy indicator given its integer ID
|
||||
|
||||
@@ -80,7 +66,7 @@ class EfficacyIndicator(base.WatcherObject):
|
||||
cls(context), db_efficacy_indicator)
|
||||
return efficacy_indicator
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find an efficacy indicator given its UUID
|
||||
|
||||
@@ -94,7 +80,7 @@ class EfficacyIndicator(base.WatcherObject):
|
||||
cls(context), db_efficacy_indicator)
|
||||
return efficacy_indicator
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def list(cls, context, limit=None, marker=None, filters=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of EfficacyIndicator objects.
|
||||
@@ -115,9 +101,11 @@ class EfficacyIndicator(base.WatcherObject):
|
||||
filters=filters,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return EfficacyIndicator._from_db_object_list(
|
||||
db_efficacy_indicators, cls, context)
|
||||
|
||||
return [cls._from_db_object(cls(context), obj)
|
||||
for obj in db_efficacy_indicators]
|
||||
|
||||
@base.remotable
|
||||
def create(self, context=None):
|
||||
"""Create a EfficacyIndicator record in the DB.
|
||||
|
||||
@@ -146,6 +134,7 @@ class EfficacyIndicator(base.WatcherObject):
|
||||
self.dbapi.destroy_efficacy_indicator(self.uuid)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def save(self, context=None):
|
||||
"""Save updates to this EfficacyIndicator.
|
||||
|
||||
@@ -164,6 +153,7 @@ class EfficacyIndicator(base.WatcherObject):
|
||||
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def refresh(self, context=None):
|
||||
"""Loads updates for this EfficacyIndicator.
|
||||
|
||||
@@ -179,11 +169,9 @@ class EfficacyIndicator(base.WatcherObject):
|
||||
object, e.g.: EfficacyIndicator(context)
|
||||
"""
|
||||
current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
|
||||
for field in self.fields:
|
||||
if (hasattr(self, base.get_attrname(field)) and
|
||||
self[field] != current[field]):
|
||||
self[field] = current[field]
|
||||
self.obj_refresh(current)
|
||||
|
||||
@base.remotable
|
||||
def soft_delete(self, context=None):
|
||||
"""Soft Delete the efficacy indicator from the DB.
|
||||
|
||||
|
89
watcher/objects/fields.py
Normal file
89
watcher/objects/fields.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# Copyright 2013 IBM Corp.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Utility methods for objects"""
|
||||
|
||||
import ast
|
||||
import six
|
||||
|
||||
from oslo_log import log
|
||||
from oslo_versionedobjects import fields
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
IntegerField = fields.IntegerField
|
||||
UUIDField = fields.UUIDField
|
||||
StringField = fields.StringField
|
||||
DateTimeField = fields.DateTimeField
|
||||
BooleanField = fields.BooleanField
|
||||
ListOfStringsField = fields.ListOfStringsField
|
||||
|
||||
|
||||
class Numeric(fields.FieldType):
|
||||
@staticmethod
|
||||
def coerce(obj, attr, value):
|
||||
if value is None:
|
||||
return value
|
||||
f_value = float(value)
|
||||
return f_value if not f_value.is_integer() else value
|
||||
|
||||
|
||||
class NumericField(fields.AutoTypedField):
|
||||
AUTO_TYPE = Numeric()
|
||||
|
||||
|
||||
class DictField(fields.AutoTypedField):
|
||||
AUTO_TYPE = fields.Dict(fields.FieldType())
|
||||
|
||||
|
||||
class FlexibleDict(fields.FieldType):
|
||||
@staticmethod
|
||||
def coerce(obj, attr, value):
|
||||
if isinstance(value, six.string_types):
|
||||
value = ast.literal_eval(value)
|
||||
return dict(value)
|
||||
|
||||
|
||||
class FlexibleDictField(fields.AutoTypedField):
|
||||
AUTO_TYPE = FlexibleDict()
|
||||
|
||||
# TODO(lucasagomes): In our code we've always translated None to {},
|
||||
# this method makes this field to work like this. But probably won't
|
||||
# be accepted as-is in the oslo_versionedobjects library
|
||||
def _null(self, obj, attr):
|
||||
if self.nullable:
|
||||
return {}
|
||||
super(FlexibleDictField, self)._null(obj, attr)
|
||||
|
||||
|
||||
class FlexibleListOfDict(fields.FieldType):
|
||||
@staticmethod
|
||||
def coerce(obj, attr, value):
|
||||
if isinstance(value, six.string_types):
|
||||
value = ast.literal_eval(value)
|
||||
return list(value)
|
||||
|
||||
|
||||
class FlexibleListOfDictField(fields.AutoTypedField):
|
||||
AUTO_TYPE = FlexibleListOfDict()
|
||||
|
||||
# TODO(lucasagomes): In our code we've always translated None to {},
|
||||
# this method makes this field to work like this. But probably won't
|
||||
# be accepted as-is in the oslo_versionedobjects library
|
||||
def _null(self, obj, attr):
|
||||
if self.nullable:
|
||||
return []
|
||||
super(FlexibleListOfDictField, self)._null(obj, attr)
|
@@ -16,40 +16,28 @@
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api as dbapi
|
||||
from watcher.db import api as db_api
|
||||
from watcher.objects import base
|
||||
from watcher.objects import utils as obj_utils
|
||||
from watcher.objects import fields as wfields
|
||||
|
||||
|
||||
class Goal(base.WatcherObject):
|
||||
@base.WatcherObjectRegistry.register
|
||||
class Goal(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
dbapi = db_api.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': int,
|
||||
'uuid': obj_utils.str_or_none,
|
||||
'name': obj_utils.str_or_none,
|
||||
'display_name': obj_utils.str_or_none,
|
||||
'efficacy_specification': obj_utils.list_or_none,
|
||||
'id': wfields.IntegerField(),
|
||||
'uuid': wfields.UUIDField(),
|
||||
'name': wfields.StringField(),
|
||||
'display_name': wfields.StringField(),
|
||||
'efficacy_specification': wfields.FlexibleListOfDictField(),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(goal, db_goal):
|
||||
"""Converts a database entity to a formal object."""
|
||||
for field in goal.fields:
|
||||
goal[field] = db_goal[field]
|
||||
|
||||
goal.obj_reset_changes()
|
||||
return goal
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object_list(db_objects, cls, context):
|
||||
"""Converts a list of database entities to a list of formal objects."""
|
||||
return [cls._from_db_object(cls(context), obj) for obj in db_objects]
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, goal_id):
|
||||
"""Find a goal based on its id or uuid
|
||||
|
||||
@@ -69,7 +57,7 @@ class Goal(base.WatcherObject):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=goal_id)
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_id(cls, context, goal_id):
|
||||
"""Find a goal based on its integer id
|
||||
|
||||
@@ -86,7 +74,7 @@ class Goal(base.WatcherObject):
|
||||
goal = cls._from_db_object(cls(context), db_goal)
|
||||
return goal
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find a goal based on uuid
|
||||
|
||||
@@ -99,12 +87,11 @@ class Goal(base.WatcherObject):
|
||||
:param uuid: the uuid of a goal.
|
||||
:returns: a :class:`Goal` object.
|
||||
"""
|
||||
|
||||
db_goal = cls.dbapi.get_goal_by_uuid(context, uuid)
|
||||
goal = cls._from_db_object(cls(context), db_goal)
|
||||
return goal
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_name(cls, context, name):
|
||||
"""Find a goal based on name
|
||||
|
||||
@@ -112,12 +99,11 @@ class Goal(base.WatcherObject):
|
||||
:param context: Security context
|
||||
:returns: a :class:`Goal` object.
|
||||
"""
|
||||
|
||||
db_goal = cls.dbapi.get_goal_by_name(context, name)
|
||||
goal = cls._from_db_object(cls(context), db_goal)
|
||||
return goal
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def list(cls, context, limit=None, marker=None, filters=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of :class:`Goal` objects.
|
||||
@@ -142,20 +128,22 @@ class Goal(base.WatcherObject):
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return cls._from_db_object_list(db_goals, cls, context)
|
||||
|
||||
return [cls._from_db_object(cls(context), obj) for obj in db_goals]
|
||||
|
||||
@base.remotable
|
||||
def create(self):
|
||||
"""Create a :class:`Goal` record in the DB."""
|
||||
|
||||
"""Create a :class:`Goal` record in the DB"""
|
||||
values = self.obj_get_changes()
|
||||
db_goal = self.dbapi.create_goal(values)
|
||||
self._from_db_object(self, db_goal)
|
||||
|
||||
def destroy(self):
|
||||
"""Delete the :class:`Goal` from the DB."""
|
||||
"""Delete the :class:`Goal` from the DB"""
|
||||
self.dbapi.destroy_goal(self.id)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def save(self):
|
||||
"""Save updates to this :class:`Goal`.
|
||||
|
||||
@@ -167,6 +155,7 @@ class Goal(base.WatcherObject):
|
||||
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def refresh(self):
|
||||
"""Loads updates for this :class:`Goal`.
|
||||
|
||||
@@ -175,11 +164,9 @@ class Goal(base.WatcherObject):
|
||||
the loaded goal column by column, if there are any updates.
|
||||
"""
|
||||
current = self.get_by_uuid(self._context, uuid=self.uuid)
|
||||
for field in self.fields:
|
||||
if (hasattr(self, base.get_attrname(field)) and
|
||||
self[field] != current[field]):
|
||||
self[field] = current[field]
|
||||
self.obj_refresh(current)
|
||||
|
||||
@base.remotable
|
||||
def soft_delete(self):
|
||||
"""Soft Delete the :class:`Goal` from the DB."""
|
||||
"""Soft Delete the :class:`Goal` from the DB"""
|
||||
self.dbapi.soft_delete_goal(self.uuid)
|
||||
|
@@ -26,42 +26,28 @@ be needed by the user of a given scoring engine.
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api as dbapi
|
||||
from watcher.db import api as db_api
|
||||
from watcher.objects import base
|
||||
from watcher.objects import utils as obj_utils
|
||||
from watcher.objects import fields as wfields
|
||||
|
||||
|
||||
class ScoringEngine(base.WatcherObject):
|
||||
@base.WatcherObjectRegistry.register
|
||||
class ScoringEngine(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
dbapi = db_api.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': int,
|
||||
'uuid': obj_utils.str_or_none,
|
||||
'name': obj_utils.str_or_none,
|
||||
'description': obj_utils.str_or_none,
|
||||
'metainfo': obj_utils.str_or_none,
|
||||
'id': wfields.IntegerField(),
|
||||
'uuid': wfields.UUIDField(),
|
||||
'name': wfields.StringField(),
|
||||
'description': wfields.StringField(nullable=True),
|
||||
'metainfo': wfields.StringField(nullable=True),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(scoring_engine, db_scoring_engine):
|
||||
"""Converts a database entity to a formal object."""
|
||||
for field in scoring_engine.fields:
|
||||
scoring_engine[field] = db_scoring_engine[field]
|
||||
|
||||
scoring_engine.obj_reset_changes()
|
||||
return scoring_engine
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object_list(db_objects, cls, context):
|
||||
"""Converts a list of database entities to a list of formal objects."""
|
||||
return \
|
||||
[ScoringEngine._from_db_object(cls(context), obj)
|
||||
for obj in db_objects]
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, scoring_engine_id):
|
||||
"""Find a scoring engine based on its id or uuid
|
||||
|
||||
@@ -81,7 +67,7 @@ class ScoringEngine(base.WatcherObject):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=scoring_engine_id)
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_id(cls, context, scoring_engine_id):
|
||||
"""Find a scoring engine based on its id
|
||||
|
||||
@@ -94,7 +80,6 @@ class ScoringEngine(base.WatcherObject):
|
||||
:param scoring_engine_id: the id of a scoring_engine.
|
||||
:returns: a :class:`ScoringEngine` object.
|
||||
"""
|
||||
|
||||
db_scoring_engine = cls.dbapi.get_scoring_engine_by_id(
|
||||
context,
|
||||
scoring_engine_id)
|
||||
@@ -102,7 +87,7 @@ class ScoringEngine(base.WatcherObject):
|
||||
db_scoring_engine)
|
||||
return scoring_engine
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_uuid(cls, context, scoring_engine_uuid):
|
||||
"""Find a scoring engine based on its uuid
|
||||
|
||||
@@ -115,7 +100,6 @@ class ScoringEngine(base.WatcherObject):
|
||||
:param scoring_engine_uuid: the uuid of a scoring_engine.
|
||||
:returns: a :class:`ScoringEngine` object.
|
||||
"""
|
||||
|
||||
db_scoring_engine = cls.dbapi.get_scoring_engine_by_uuid(
|
||||
context,
|
||||
scoring_engine_uuid)
|
||||
@@ -123,7 +107,7 @@ class ScoringEngine(base.WatcherObject):
|
||||
db_scoring_engine)
|
||||
return scoring_engine
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_name(cls, context, scoring_engine_name):
|
||||
"""Find a scoring engine based on its name
|
||||
|
||||
@@ -136,7 +120,6 @@ class ScoringEngine(base.WatcherObject):
|
||||
:param scoring_engine_name: the name of a scoring_engine.
|
||||
:returns: a :class:`ScoringEngine` object.
|
||||
"""
|
||||
|
||||
db_scoring_engine = cls.dbapi.get_scoring_engine_by_name(
|
||||
context,
|
||||
scoring_engine_name)
|
||||
@@ -144,7 +127,7 @@ class ScoringEngine(base.WatcherObject):
|
||||
db_scoring_engine)
|
||||
return scoring_engine
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def list(cls, context, filters=None, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of :class:`ScoringEngine` objects.
|
||||
@@ -162,7 +145,6 @@ class ScoringEngine(base.WatcherObject):
|
||||
:param sort_dir: direction to sort. "asc" or "desc".
|
||||
:returns: a list of :class:`ScoringEngine` objects.
|
||||
"""
|
||||
|
||||
db_scoring_engines = cls.dbapi.get_scoring_engine_list(
|
||||
context,
|
||||
filters=filters,
|
||||
@@ -170,88 +152,43 @@ class ScoringEngine(base.WatcherObject):
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return ScoringEngine._from_db_object_list(db_scoring_engines,
|
||||
cls, context)
|
||||
|
||||
def create(self, context=None):
|
||||
"""Create a :class:`ScoringEngine` record in the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: ScoringEngine(context)
|
||||
"""
|
||||
return [cls._from_db_object(cls(context), obj)
|
||||
for obj in db_scoring_engines]
|
||||
|
||||
@base.remotable
|
||||
def create(self):
|
||||
"""Create a :class:`ScoringEngine` record in the DB."""
|
||||
values = self.obj_get_changes()
|
||||
db_scoring_engine = self.dbapi.create_scoring_engine(values)
|
||||
self._from_db_object(self, db_scoring_engine)
|
||||
|
||||
def destroy(self, context=None):
|
||||
"""Delete the :class:`ScoringEngine` from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: ScoringEngine(context)
|
||||
"""
|
||||
|
||||
def destroy(self):
|
||||
"""Delete the :class:`ScoringEngine` from the DB"""
|
||||
self.dbapi.destroy_scoring_engine(self.id)
|
||||
self.obj_reset_changes()
|
||||
|
||||
def save(self, context=None):
|
||||
@base.remotable
|
||||
def save(self):
|
||||
"""Save updates to this :class:`ScoringEngine`.
|
||||
|
||||
Updates will be made column by column based on the result
|
||||
of self.what_changed().
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: ScoringEngine(context)
|
||||
"""
|
||||
|
||||
updates = self.obj_get_changes()
|
||||
self.dbapi.update_scoring_engine(self.id, updates)
|
||||
|
||||
self.obj_reset_changes()
|
||||
|
||||
def refresh(self, context=None):
|
||||
def refresh(self):
|
||||
"""Loads updates for this :class:`ScoringEngine`.
|
||||
|
||||
Loads a scoring_engine with the same id from the database and
|
||||
checks for updated attributes. Updates are applied from
|
||||
the loaded scoring_engine column by column, if there are any updates.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: ScoringEngine(context)
|
||||
"""
|
||||
|
||||
current = self.__class__.get_by_id(self._context,
|
||||
scoring_engine_id=self.id)
|
||||
for field in self.fields:
|
||||
if (hasattr(self, base.get_attrname(field)) and
|
||||
self[field] != current[field]):
|
||||
self[field] = current[field]
|
||||
|
||||
def soft_delete(self, context=None):
|
||||
"""soft Delete the :class:`ScoringEngine` from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: ScoringEngine(context)
|
||||
"""
|
||||
current = self.get_by_id(self._context, scoring_engine_id=self.id)
|
||||
self.obj_refresh(current)
|
||||
|
||||
def soft_delete(self):
|
||||
"""Soft Delete the :class:`ScoringEngine` from the DB"""
|
||||
self.dbapi.soft_delete_scoring_engine(self.id)
|
||||
|
@@ -16,9 +16,9 @@
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api as dbapi
|
||||
from watcher.db import api as db_api
|
||||
from watcher.objects import base
|
||||
from watcher.objects import utils as obj_utils
|
||||
from watcher.objects import fields as wfields
|
||||
|
||||
|
||||
class ServiceStatus(object):
|
||||
@@ -26,33 +26,24 @@ class ServiceStatus(object):
|
||||
FAILED = 'FAILED'
|
||||
|
||||
|
||||
class Service(base.WatcherObject):
|
||||
@base.WatcherObjectRegistry.register
|
||||
class Service(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
dbapi = db_api.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': int,
|
||||
'name': obj_utils.str_or_none,
|
||||
'host': obj_utils.str_or_none,
|
||||
'last_seen_up': obj_utils.datetime_or_str_or_none
|
||||
'id': wfields.IntegerField(),
|
||||
'name': wfields.StringField(),
|
||||
'host': wfields.StringField(),
|
||||
'last_seen_up': wfields.DateTimeField(
|
||||
tzinfo_aware=False, nullable=True),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(service, db_service):
|
||||
"""Converts a database entity to a formal object."""
|
||||
for field in service.fields:
|
||||
service[field] = db_service[field]
|
||||
|
||||
service.obj_reset_changes()
|
||||
return service
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object_list(db_objects, cls, context):
|
||||
"""Converts a list of database entities to a list of formal objects."""
|
||||
return [Service._from_db_object(cls(context), obj)
|
||||
for obj in db_objects]
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, service_id):
|
||||
"""Find a service based on its id
|
||||
|
||||
@@ -72,7 +63,7 @@ class Service(base.WatcherObject):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=service_id)
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_name(cls, context, name):
|
||||
"""Find a service based on name
|
||||
|
||||
@@ -85,7 +76,7 @@ class Service(base.WatcherObject):
|
||||
service = cls._from_db_object(cls(context), db_service)
|
||||
return service
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def list(cls, context, limit=None, marker=None, filters=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of :class:`Service` objects.
|
||||
@@ -110,69 +101,41 @@ class Service(base.WatcherObject):
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return Service._from_db_object_list(db_services, cls, context)
|
||||
|
||||
def create(self, context=None):
|
||||
"""Create a :class:`Service` record in the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Service(context)
|
||||
"""
|
||||
return [cls._from_db_object(cls(context), obj) for obj in db_services]
|
||||
|
||||
@base.remotable
|
||||
def create(self):
|
||||
"""Create a :class:`Service` record in the DB."""
|
||||
values = self.obj_get_changes()
|
||||
db_service = self.dbapi.create_service(values)
|
||||
self._from_db_object(self, db_service)
|
||||
|
||||
def save(self, context=None):
|
||||
@base.remotable
|
||||
def save(self):
|
||||
"""Save updates to this :class:`Service`.
|
||||
|
||||
Updates will be made column by column based on the result
|
||||
of self.what_changed().
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Service(context)
|
||||
"""
|
||||
updates = self.obj_get_changes()
|
||||
self.dbapi.update_service(self.id, updates)
|
||||
|
||||
self.obj_reset_changes()
|
||||
|
||||
def refresh(self, context=None):
|
||||
def refresh(self):
|
||||
"""Loads updates for this :class:`Service`.
|
||||
|
||||
Loads a service with the same id from the database and
|
||||
checks for updated attributes. Updates are applied from
|
||||
the loaded service column by column, if there are any updates.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Service(context)
|
||||
"""
|
||||
current = self.__class__.get(self._context, service_id=self.id)
|
||||
current = self.get(self._context, service_id=self.id)
|
||||
for field in self.fields:
|
||||
if (hasattr(self, base.get_attrname(field)) and
|
||||
self[field] != current[field]):
|
||||
self[field] = current[field]
|
||||
|
||||
def soft_delete(self, context=None):
|
||||
"""Soft Delete the :class:`Service` from the DB.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Service(context)
|
||||
"""
|
||||
def soft_delete(self):
|
||||
"""Soft Delete the :class:`Service` from the DB."""
|
||||
self.dbapi.soft_delete_service(self.id)
|
||||
|
@@ -16,40 +16,27 @@
|
||||
|
||||
from watcher.common import exception
|
||||
from watcher.common import utils
|
||||
from watcher.db import api as dbapi
|
||||
from watcher.db import api as db_api
|
||||
from watcher.objects import base
|
||||
from watcher.objects import utils as obj_utils
|
||||
from watcher.objects import fields as wfields
|
||||
|
||||
|
||||
class Strategy(base.WatcherObject):
|
||||
@base.WatcherObjectRegistry.register
|
||||
class Strategy(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
dbapi = db_api.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': int,
|
||||
'uuid': obj_utils.str_or_none,
|
||||
'name': obj_utils.str_or_none,
|
||||
'display_name': obj_utils.str_or_none,
|
||||
'goal_id': obj_utils.int_or_none,
|
||||
'parameters_spec': obj_utils.dict_or_none,
|
||||
'id': wfields.IntegerField(),
|
||||
'uuid': wfields.UUIDField(),
|
||||
'name': wfields.StringField(),
|
||||
'display_name': wfields.StringField(),
|
||||
'goal_id': wfields.IntegerField(),
|
||||
'parameters_spec': wfields.FlexibleDictField(nullable=True),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object(strategy, db_strategy):
|
||||
"""Converts a database entity to a formal object."""
|
||||
for field in strategy.fields:
|
||||
strategy[field] = db_strategy[field]
|
||||
|
||||
strategy.obj_reset_changes()
|
||||
return strategy
|
||||
|
||||
@staticmethod
|
||||
def _from_db_object_list(db_objects, cls, context):
|
||||
"""Converts a list of database entities to a list of formal objects."""
|
||||
return [Strategy._from_db_object(cls(context), obj)
|
||||
for obj in db_objects]
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get(cls, context, strategy_id):
|
||||
"""Find a strategy based on its id or uuid
|
||||
|
||||
@@ -69,7 +56,7 @@ class Strategy(base.WatcherObject):
|
||||
else:
|
||||
raise exception.InvalidIdentity(identity=strategy_id)
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_id(cls, context, strategy_id):
|
||||
"""Find a strategy based on its integer id
|
||||
|
||||
@@ -86,7 +73,7 @@ class Strategy(base.WatcherObject):
|
||||
strategy = Strategy._from_db_object(cls(context), db_strategy)
|
||||
return strategy
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find a strategy based on uuid
|
||||
|
||||
@@ -104,7 +91,7 @@ class Strategy(base.WatcherObject):
|
||||
strategy = cls._from_db_object(cls(context), db_strategy)
|
||||
return strategy
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def get_by_name(cls, context, name):
|
||||
"""Find a strategy based on name
|
||||
|
||||
@@ -117,7 +104,7 @@ class Strategy(base.WatcherObject):
|
||||
strategy = cls._from_db_object(cls(context), db_strategy)
|
||||
return strategy
|
||||
|
||||
@classmethod
|
||||
@base.remotable_classmethod
|
||||
def list(cls, context, limit=None, marker=None, filters=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Return a list of :class:`Strategy` objects.
|
||||
@@ -142,8 +129,11 @@ class Strategy(base.WatcherObject):
|
||||
marker=marker,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
return Strategy._from_db_object_list(db_strategies, cls, context)
|
||||
|
||||
return [cls._from_db_object(cls(context), obj)
|
||||
for obj in db_strategies]
|
||||
|
||||
@base.remotable
|
||||
def create(self, context=None):
|
||||
"""Create a :class:`Strategy` record in the DB.
|
||||
|
||||
@@ -172,6 +162,7 @@ class Strategy(base.WatcherObject):
|
||||
self.dbapi.destroy_strategy(self.id)
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def save(self, context=None):
|
||||
"""Save updates to this :class:`Strategy`.
|
||||
|
||||
@@ -190,6 +181,7 @@ class Strategy(base.WatcherObject):
|
||||
|
||||
self.obj_reset_changes()
|
||||
|
||||
@base.remotable
|
||||
def refresh(self, context=None):
|
||||
"""Loads updates for this :class:`Strategy`.
|
||||
|
||||
@@ -210,6 +202,7 @@ class Strategy(base.WatcherObject):
|
||||
self[field] != current[field]):
|
||||
self[field] = current[field]
|
||||
|
||||
@base.remotable
|
||||
def soft_delete(self, context=None):
|
||||
"""Soft Delete the :class:`Strategy` from the DB.
|
||||
|
||||
|
@@ -0,0 +1,22 @@
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from watcher import objects
|
||||
|
||||
# NOTE(comstud): Make sure we have all of the objects loaded. We do this
|
||||
# at module import time, because we may be using mock decorators in our
|
||||
# tests that run at import time.
|
||||
objects.register_all()
|
||||
|
@@ -64,7 +64,7 @@ class FunctionalTest(base.DbTestCase):
|
||||
|
||||
def _make_app(self, enable_acl=False):
|
||||
# Determine where we are so we can set up paths in the config
|
||||
root_dir = self.path_get()
|
||||
root_dir = self.get_path()
|
||||
|
||||
self.config = {
|
||||
'app': {
|
||||
|
@@ -250,7 +250,7 @@ class TestPatch(api_base.FunctionalTest):
|
||||
def setUp(self):
|
||||
super(TestPatch, self).setUp()
|
||||
obj_utils.create_test_audit_template(self.context)
|
||||
self.audit = obj_utils.create_test_audit(self.context)
|
||||
self.audit = obj_utils.create_test_audit(self.context, )
|
||||
p = mock.patch.object(db_api.BaseConnection, 'update_audit')
|
||||
self.mock_audit_update = p.start()
|
||||
self.mock_audit_update.side_effect = self._simulate_rpc_audit_update
|
||||
@@ -313,15 +313,15 @@ class TestPatch(api_base.FunctionalTest):
|
||||
|
||||
def test_remove_ok(self):
|
||||
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||
self.assertIsNotNone(response['state'])
|
||||
self.assertIsNotNone(response['interval'])
|
||||
|
||||
response = self.patch_json('/audits/%s' % self.audit.uuid,
|
||||
[{'path': '/state', 'op': 'remove'}])
|
||||
[{'path': '/interval', 'op': 'remove'}])
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||
self.assertIsNone(response['state'])
|
||||
self.assertIsNone(response['interval'])
|
||||
|
||||
def test_remove_uuid(self):
|
||||
response = self.patch_json('/audits/%s' % self.audit.uuid,
|
||||
|
@@ -80,7 +80,7 @@ class TestDefaultWorkFlowEngine(base.DbTestCase):
|
||||
'next': next,
|
||||
}
|
||||
new_action = objects.Action(self.context, **action)
|
||||
new_action.create(self.context)
|
||||
new_action.create()
|
||||
new_action.save()
|
||||
|
||||
return new_action
|
||||
|
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2010-2011 OpenStack Foundation
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
@@ -35,8 +33,11 @@ from watcher.tests import policy_fixture
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
log.register_options(CONF)
|
||||
CONF.set_override('use_stderr', False, enforce_type=True)
|
||||
try:
|
||||
log.register_options(CONF)
|
||||
except cfg.ArgsAlreadyParsedError:
|
||||
pass
|
||||
CONF.set_override('use_stderr', False)
|
||||
|
||||
|
||||
class BaseTestCase(testscenarios.WithScenarios, base.BaseTestCase):
|
||||
@@ -76,6 +77,9 @@ class TestCase(BaseTestCase):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
objects_base.WatcherObject.indirection_api = None
|
||||
|
||||
self.context = watcher_context.RequestContext(
|
||||
auth_token_info=self.token_info,
|
||||
project_id='fake_project',
|
||||
@@ -104,19 +108,21 @@ class TestCase(BaseTestCase):
|
||||
self._reset_singletons()
|
||||
|
||||
self._base_test_obj_backup = copy.copy(
|
||||
objects_base.WatcherObject._obj_classes)
|
||||
objects_base.WatcherObjectRegistry._registry._obj_classes)
|
||||
self.addCleanup(self._restore_obj_registry)
|
||||
self.addCleanup(self._reset_singletons)
|
||||
|
||||
def _reset_singletons(self):
|
||||
service.Singleton._instances.clear()
|
||||
|
||||
def _restore_obj_registry(self):
|
||||
objects_base.WatcherObject._obj_classes = self._base_test_obj_backup
|
||||
def reset_pecan():
|
||||
pecan.set_config({}, overwrite=True)
|
||||
|
||||
def tearDown(self):
|
||||
super(TestCase, self).tearDown()
|
||||
pecan.set_config({}, overwrite=True)
|
||||
self.addCleanup(reset_pecan)
|
||||
|
||||
def _restore_obj_registry(self):
|
||||
objects_base.WatcherObjectRegistry._registry._obj_classes = (
|
||||
self._base_test_obj_backup)
|
||||
|
||||
def config(self, **kw):
|
||||
"""Override config options for a test."""
|
||||
@@ -124,7 +130,7 @@ class TestCase(BaseTestCase):
|
||||
for k, v in kw.items():
|
||||
CONF.set_override(k, v, group, enforce_type=True)
|
||||
|
||||
def path_get(self, project_file=None):
|
||||
def get_path(self, project_file=None):
|
||||
"""Get the absolute path to a file. Used for testing the API.
|
||||
|
||||
:param project_file: File whose path to return. Default: None.
|
||||
|
@@ -20,6 +20,7 @@ import freezegun
|
||||
import mock
|
||||
|
||||
from watcher.common import context as watcher_context
|
||||
from watcher.common import utils
|
||||
from watcher.db import purge
|
||||
from watcher.db.sqlalchemy import api as dbapi
|
||||
from watcher.tests.db import base
|
||||
@@ -101,27 +102,33 @@ class TestPurgeCommand(base.DbTestCase):
|
||||
|
||||
with freezegun.freeze_time(self.expired_date):
|
||||
self.goal1 = obj_utils.create_test_goal(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
self.context, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(),
|
||||
name=goal1_name, display_name=goal1_name.lower())
|
||||
self.goal2 = obj_utils.create_test_goal(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
self.context, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(),
|
||||
name=goal2_name, display_name=goal2_name.lower())
|
||||
self.goal3 = obj_utils.create_test_goal(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
self.context, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(),
|
||||
name=goal3_name, display_name=goal3_name.lower())
|
||||
self.goal1.soft_delete()
|
||||
|
||||
with freezegun.freeze_time(self.expired_date):
|
||||
self.strategy1 = obj_utils.create_test_strategy(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
self.context, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(),
|
||||
name=strategy1_name, display_name=strategy1_name.lower(),
|
||||
goal_id=self.goal1.id)
|
||||
self.strategy2 = obj_utils.create_test_strategy(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
self.context, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(),
|
||||
name=strategy2_name, display_name=strategy2_name.lower(),
|
||||
goal_id=self.goal2.id)
|
||||
self.strategy3 = obj_utils.create_test_strategy(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
self.context, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(),
|
||||
name=strategy3_name, display_name=strategy3_name.lower(),
|
||||
goal_id=self.goal3.id)
|
||||
self.strategy1.soft_delete()
|
||||
@@ -129,50 +136,61 @@ class TestPurgeCommand(base.DbTestCase):
|
||||
with freezegun.freeze_time(self.expired_date):
|
||||
self.audit_template1 = obj_utils.create_test_audit_template(
|
||||
self.context, name=self.audit_template1_name,
|
||||
id=self._generate_id(), uuid=None, goal_id=self.goal1.id,
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(), goal_id=self.goal1.id,
|
||||
strategy_id=self.strategy1.id)
|
||||
self.audit_template2 = obj_utils.create_test_audit_template(
|
||||
self.context, name=self.audit_template2_name,
|
||||
id=self._generate_id(), uuid=None, goal_id=self.goal2.id,
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(), goal_id=self.goal2.id,
|
||||
strategy_id=self.strategy2.id)
|
||||
self.audit_template3 = obj_utils.create_test_audit_template(
|
||||
self.context, name=self.audit_template3_name,
|
||||
id=self._generate_id(), uuid=None, goal_id=self.goal3.id,
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(), goal_id=self.goal3.id,
|
||||
strategy_id=self.strategy3.id)
|
||||
self.audit_template1.soft_delete()
|
||||
|
||||
with freezegun.freeze_time(self.expired_date):
|
||||
self.audit1 = obj_utils.create_test_audit(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
self.context, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(),
|
||||
goal_id=self.goal1.id, strategy_id=self.strategy1.id)
|
||||
self.audit2 = obj_utils.create_test_audit(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
self.context, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(),
|
||||
goal_id=self.goal2.id, strategy_id=self.strategy2.id)
|
||||
self.audit3 = obj_utils.create_test_audit(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
self.context, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid(),
|
||||
goal_id=self.goal3.id, strategy_id=self.strategy3.id)
|
||||
self.audit1.soft_delete()
|
||||
|
||||
with freezegun.freeze_time(self.expired_date):
|
||||
self.action_plan1 = obj_utils.create_test_action_plan(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
audit_id=self.audit1.id, strategy_id=self.strategy1.id)
|
||||
self.context, audit_id=self.audit1.id,
|
||||
id=self._generate_id(), uuid=utils.generate_uuid(),
|
||||
strategy_id=self.strategy1.id)
|
||||
self.action_plan2 = obj_utils.create_test_action_plan(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
audit_id=self.audit2.id, strategy_id=self.strategy2.id)
|
||||
self.context, audit_id=self.audit2.id,
|
||||
id=self._generate_id(),
|
||||
strategy_id=self.strategy2.id,
|
||||
uuid=utils.generate_uuid())
|
||||
self.action_plan3 = obj_utils.create_test_action_plan(
|
||||
self.context, id=self._generate_id(), uuid=None,
|
||||
audit_id=self.audit3.id, strategy_id=self.strategy3.id)
|
||||
self.context, audit_id=self.audit3.id,
|
||||
id=self._generate_id(), uuid=utils.generate_uuid(),
|
||||
strategy_id=self.strategy3.id)
|
||||
|
||||
self.action1 = obj_utils.create_test_action(
|
||||
self.context, action_plan_id=self.action_plan1.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
self.action2 = obj_utils.create_test_action(
|
||||
self.context, action_plan_id=self.action_plan2.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(), uuid=utils.generate_uuid())
|
||||
self.action3 = obj_utils.create_test_action(
|
||||
self.context, action_plan_id=self.action_plan3.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(), uuid=utils.generate_uuid())
|
||||
self.action_plan1.soft_delete()
|
||||
|
||||
@mock.patch.object(dbapi.Connection, "destroy_action")
|
||||
@@ -249,30 +267,38 @@ class TestPurgeCommand(base.DbTestCase):
|
||||
audit_template4 = obj_utils.create_test_audit_template(
|
||||
self.context, goal_id=404, # Does not exist
|
||||
name=self.generate_unique_name(prefix="Audit Template 4 "),
|
||||
strategy_id=None, id=self._generate_id(), uuid=None)
|
||||
strategy_id=None, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
audit4 = obj_utils.create_test_audit(
|
||||
self.context, audit_template_id=audit_template4.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
action_plan4 = obj_utils.create_test_action_plan(
|
||||
self.context, audit_id=audit4.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
action4 = obj_utils.create_test_action(
|
||||
self.context, action_plan_id=action_plan4.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
|
||||
audit_template5 = obj_utils.create_test_audit_template(
|
||||
self.context, goal_id=self.goal1.id,
|
||||
name=self.generate_unique_name(prefix="Audit Template 5 "),
|
||||
strategy_id=None, id=self._generate_id(), uuid=None)
|
||||
strategy_id=None, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
audit5 = obj_utils.create_test_audit(
|
||||
self.context, audit_template_id=audit_template5.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
action_plan5 = obj_utils.create_test_action_plan(
|
||||
self.context, audit_id=audit5.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
action5 = obj_utils.create_test_action(
|
||||
self.context, action_plan_id=action_plan5.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
|
||||
self.goal2.soft_delete()
|
||||
self.strategy2.soft_delete()
|
||||
@@ -338,30 +364,38 @@ class TestPurgeCommand(base.DbTestCase):
|
||||
audit_template4 = obj_utils.create_test_audit_template(
|
||||
self.context, goal_id=404, # Does not exist
|
||||
name=self.generate_unique_name(prefix="Audit Template 4 "),
|
||||
strategy_id=None, id=self._generate_id(), uuid=None)
|
||||
strategy_id=None, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
audit4 = obj_utils.create_test_audit(
|
||||
self.context, audit_template_id=audit_template4.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
action_plan4 = obj_utils.create_test_action_plan(
|
||||
self.context, audit_id=audit4.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
action4 = obj_utils.create_test_action(
|
||||
self.context, action_plan_id=action_plan4.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
|
||||
audit_template5 = obj_utils.create_test_audit_template(
|
||||
self.context, goal_id=self.goal1.id,
|
||||
name=self.generate_unique_name(prefix="Audit Template 5 "),
|
||||
strategy_id=None, id=self._generate_id(), uuid=None)
|
||||
strategy_id=None, id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
audit5 = obj_utils.create_test_audit(
|
||||
self.context, audit_template_id=audit_template5.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
action_plan5 = obj_utils.create_test_action_plan(
|
||||
self.context, audit_id=audit5.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
action5 = obj_utils.create_test_action(
|
||||
self.context, action_plan_id=action_plan5.id,
|
||||
id=self._generate_id(), uuid=None)
|
||||
id=self._generate_id(),
|
||||
uuid=utils.generate_uuid())
|
||||
|
||||
self.goal2.soft_delete()
|
||||
self.strategy2.soft_delete()
|
||||
|
@@ -56,7 +56,7 @@ def get_test_audit(**kwargs):
|
||||
'id': kwargs.get('id', 1),
|
||||
'uuid': kwargs.get('uuid', '10a47dd1-4874-4298-91cf-eff046dbdb8d'),
|
||||
'audit_type': kwargs.get('audit_type', 'ONESHOT'),
|
||||
'state': kwargs.get('state'),
|
||||
'state': kwargs.get('state', objects.audit.State.PENDING),
|
||||
'created_at': kwargs.get('created_at'),
|
||||
'updated_at': kwargs.get('updated_at'),
|
||||
'deleted_at': kwargs.get('deleted_at'),
|
||||
|
@@ -21,7 +21,6 @@ from watcher.common import utils
|
||||
from watcher.decision_engine.loading import default
|
||||
from watcher.decision_engine import sync
|
||||
from watcher import objects
|
||||
from watcher.objects import action_plan as ap_objects
|
||||
from watcher.tests.db import base
|
||||
from watcher.tests.decision_engine import fake_goals
|
||||
from watcher.tests.decision_engine import fake_strategies
|
||||
@@ -329,22 +328,30 @@ class TestSyncer(base.DbTestCase):
|
||||
# Should stay unmodified after sync()
|
||||
audit1 = objects.Audit(
|
||||
self.ctx, id=1, uuid=utils.generate_uuid(),
|
||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||
state=objects.audit.State.PENDING,
|
||||
goal_id=goal1.id, strategy_id=strategy1.id)
|
||||
# Should be modified by the sync() because its associated goal
|
||||
# has been modified (compared to the defined fake goals)
|
||||
audit2 = objects.Audit(
|
||||
self.ctx, id=2, uuid=utils.generate_uuid(),
|
||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||
state=objects.audit.State.PENDING,
|
||||
goal_id=goal2.id, strategy_id=strategy2.id)
|
||||
# Should be modified by the sync() because its associated strategy
|
||||
# has been modified (compared to the defined fake strategies)
|
||||
audit3 = objects.Audit(
|
||||
self.ctx, id=3, uuid=utils.generate_uuid(),
|
||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||
state=objects.audit.State.PENDING,
|
||||
goal_id=goal1.id, strategy_id=strategy3.id)
|
||||
# Modified because of both because its associated goal and associated
|
||||
# strategy should be modified (compared to the defined fake
|
||||
# goals/strategies)
|
||||
audit4 = objects.Audit(
|
||||
self.ctx, id=4, uuid=utils.generate_uuid(),
|
||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||
state=objects.audit.State.PENDING,
|
||||
goal_id=goal2.id, strategy_id=strategy4.id)
|
||||
|
||||
audit1.create()
|
||||
@@ -483,7 +490,7 @@ class TestSyncer(base.DbTestCase):
|
||||
set([action_plan2.id, action_plan3.id, action_plan4.id]),
|
||||
set(modified_action_plans))
|
||||
self.assertTrue(
|
||||
all(ap.state == ap_objects.State.CANCELLED
|
||||
all(ap.state == objects.action_plan.State.CANCELLED
|
||||
for ap in modified_action_plans.values()))
|
||||
self.assertEqual(set([action_plan1.id]), set(unmodified_action_plans))
|
||||
|
||||
@@ -551,10 +558,14 @@ class TestSyncer(base.DbTestCase):
|
||||
# Should stay unmodified after sync()
|
||||
audit1 = objects.Audit(
|
||||
self.ctx, id=1, uuid=utils.generate_uuid(),
|
||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||
state=objects.audit.State.PENDING,
|
||||
goal_id=goal1.id, strategy_id=strategy1.id)
|
||||
# Stale after syncing because the goal has been soft deleted
|
||||
audit2 = objects.Audit(
|
||||
self.ctx, id=2, uuid=utils.generate_uuid(),
|
||||
audit_type=objects.audit.AuditType.ONESHOT.value,
|
||||
state=objects.audit.State.PENDING,
|
||||
goal_id=goal2.id, strategy_id=strategy2.id)
|
||||
audit1.create()
|
||||
audit2.create()
|
||||
@@ -651,6 +662,6 @@ class TestSyncer(base.DbTestCase):
|
||||
|
||||
self.assertEqual(set([action_plan2.id]), set(modified_action_plans))
|
||||
self.assertTrue(
|
||||
all(ap.state == ap_objects.State.CANCELLED
|
||||
all(ap.state == objects.action_plan.State.CANCELLED
|
||||
for ap in modified_action_plans.values()))
|
||||
self.assertEqual(set([action_plan1.id]), set(unmodified_action_plans))
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Copyright 2015 IBM Corp.
|
||||
# Copyright 2013 IBM Corp.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
@@ -12,32 +12,38 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import contextlib
|
||||
import datetime
|
||||
import gettext
|
||||
|
||||
import iso8601
|
||||
import netaddr
|
||||
from oslo_utils import timeutils
|
||||
|
||||
import mock
|
||||
from oslo_versionedobjects import base as object_base
|
||||
from oslo_versionedobjects import exception as object_exception
|
||||
from oslo_versionedobjects import fixture as object_fixture
|
||||
import six
|
||||
|
||||
from watcher.common import context
|
||||
from watcher.objects import base
|
||||
from watcher.objects import utils
|
||||
from watcher.objects import fields
|
||||
from watcher.tests import base as test_base
|
||||
|
||||
gettext.install('watcher')
|
||||
|
||||
|
||||
class MyObj(base.WatcherObject):
|
||||
VERSION = '1.0'
|
||||
@base.WatcherObjectRegistry.register
|
||||
class MyObj(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
VERSION = '1.5'
|
||||
|
||||
fields = {'foo': int,
|
||||
'bar': str,
|
||||
'missing': str,
|
||||
}
|
||||
fields = {'foo': fields.IntegerField(),
|
||||
'bar': fields.StringField(),
|
||||
'missing': fields.StringField()}
|
||||
|
||||
def obj_load_attr(self, attrname):
|
||||
setattr(self, attrname, 'loaded!')
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
def query(cls, context):
|
||||
obj = cls(context)
|
||||
obj.foo = 1
|
||||
@@ -45,24 +51,29 @@ class MyObj(base.WatcherObject):
|
||||
obj.obj_reset_changes()
|
||||
return obj
|
||||
|
||||
def marco(self, context):
|
||||
@object_base.remotable
|
||||
def marco(self, context=None):
|
||||
return 'polo'
|
||||
|
||||
def update_test(self, context):
|
||||
if context.project_id == 'alternate':
|
||||
@object_base.remotable
|
||||
def update_test(self, context=None):
|
||||
if context and context.user == 'alternate':
|
||||
self.bar = 'alternate-context'
|
||||
else:
|
||||
self.bar = 'updated'
|
||||
|
||||
def save(self, context):
|
||||
@object_base.remotable
|
||||
def save(self, context=None):
|
||||
self.obj_reset_changes()
|
||||
|
||||
def refresh(self, context):
|
||||
@object_base.remotable
|
||||
def refresh(self, context=None):
|
||||
self.foo = 321
|
||||
self.bar = 'refreshed'
|
||||
self.obj_reset_changes()
|
||||
|
||||
def modify_save_modify(self, context):
|
||||
@object_base.remotable
|
||||
def modify_save_modify(self, context=None):
|
||||
self.bar = 'meow'
|
||||
self.save()
|
||||
self.foo = 42
|
||||
@@ -73,232 +84,361 @@ class MyObj2(object):
|
||||
def obj_name(cls):
|
||||
return 'MyObj'
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
def get(cls, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class DummySubclassedObject(MyObj):
|
||||
fields = {'new_field': str}
|
||||
@base.WatcherObjectRegistry.register_if(False)
|
||||
class WatcherTestSubclassedObject(MyObj):
|
||||
fields = {'new_field': fields.StringField()}
|
||||
|
||||
|
||||
class TestMetaclass(test_base.TestCase):
|
||||
def test_obj_tracking(self):
|
||||
|
||||
@six.add_metaclass(base.WatcherObjectMetaclass)
|
||||
class NewBaseClass(object):
|
||||
fields = {}
|
||||
|
||||
@classmethod
|
||||
def obj_name(cls):
|
||||
return cls.__name__
|
||||
|
||||
class Test1(NewBaseClass):
|
||||
@staticmethod
|
||||
def obj_name():
|
||||
return 'fake1'
|
||||
|
||||
class Test2(NewBaseClass):
|
||||
pass
|
||||
|
||||
class Test2v2(NewBaseClass):
|
||||
@staticmethod
|
||||
def obj_name():
|
||||
return 'Test2'
|
||||
|
||||
expected = {'fake1': [Test1], 'Test2': [Test2, Test2v2]}
|
||||
|
||||
self.assertEqual(expected, NewBaseClass._obj_classes)
|
||||
# The following should work, also.
|
||||
self.assertEqual(expected, Test1._obj_classes)
|
||||
self.assertEqual(expected, Test2._obj_classes)
|
||||
class _LocalTest(test_base.TestCase):
|
||||
def setUp(self):
|
||||
super(_LocalTest, self).setUp()
|
||||
# Just in case
|
||||
base.WatcherObject.indirection_api = None
|
||||
|
||||
|
||||
class TestUtils(test_base.TestCase):
|
||||
|
||||
def test_datetime_or_none(self):
|
||||
naive_dt = datetime.datetime.now()
|
||||
dt = timeutils.parse_isotime(timeutils.isotime(naive_dt))
|
||||
self.assertEqual(dt, utils.datetime_or_none(dt, tzinfo_aware=True))
|
||||
self.assertEqual(naive_dt.replace(tzinfo=iso8601.iso8601.Utc(),
|
||||
microsecond=0),
|
||||
utils.datetime_or_none(dt, tzinfo_aware=True))
|
||||
self.assertIsNone(utils.datetime_or_none(None))
|
||||
self.assertRaises(ValueError, utils.datetime_or_none, 'foo')
|
||||
|
||||
def test_datetime_or_none_tzinfo_naive(self):
|
||||
naive_dt = datetime.datetime.utcnow()
|
||||
self.assertEqual(naive_dt, utils.datetime_or_none(naive_dt,
|
||||
tzinfo_aware=False))
|
||||
self.assertIsNone(utils.datetime_or_none(None))
|
||||
self.assertRaises(ValueError, utils.datetime_or_none, 'foo')
|
||||
|
||||
def test_datetime_or_str_or_none(self):
|
||||
dts = timeutils.isotime()
|
||||
dt = timeutils.parse_isotime(dts)
|
||||
self.assertEqual(dt, utils.datetime_or_str_or_none(dt,
|
||||
tzinfo_aware=True))
|
||||
self.assertIsNone(utils.datetime_or_str_or_none(None,
|
||||
tzinfo_aware=True))
|
||||
self.assertEqual(dt, utils.datetime_or_str_or_none(dts,
|
||||
tzinfo_aware=True))
|
||||
self.assertRaises(ValueError, utils.datetime_or_str_or_none, 'foo')
|
||||
|
||||
def test_int_or_none(self):
|
||||
self.assertEqual(1, utils.int_or_none(1))
|
||||
self.assertEqual(1, utils.int_or_none('1'))
|
||||
self.assertIsNone(utils.int_or_none(None))
|
||||
self.assertRaises(ValueError, utils.int_or_none, 'foo')
|
||||
|
||||
def test_str_or_none(self):
|
||||
class Obj(object):
|
||||
pass
|
||||
self.assertEqual('foo', utils.str_or_none('foo'))
|
||||
self.assertEqual('1', utils.str_or_none(1))
|
||||
self.assertIsNone(utils.str_or_none(None))
|
||||
|
||||
def test_ip_or_none(self):
|
||||
ip4 = netaddr.IPAddress('1.2.3.4', 4)
|
||||
ip6 = netaddr.IPAddress('1::2', 6)
|
||||
self.assertEqual(ip4, utils.ip_or_none(4)('1.2.3.4'))
|
||||
self.assertEqual(ip6, utils.ip_or_none(6)('1::2'))
|
||||
self.assertIsNone(utils.ip_or_none(4)(None))
|
||||
self.assertIsNone(utils.ip_or_none(6)(None))
|
||||
self.assertRaises(netaddr.AddrFormatError, utils.ip_or_none(4), 'foo')
|
||||
self.assertRaises(netaddr.AddrFormatError, utils.ip_or_none(6), 'foo')
|
||||
|
||||
def test_dt_serializer(self):
|
||||
class Obj(object):
|
||||
foo = utils.dt_serializer('bar')
|
||||
|
||||
obj = Obj()
|
||||
obj.bar = timeutils.parse_isotime('1955-11-05T00:00:00Z')
|
||||
self.assertEqual('1955-11-05T00:00:00Z', obj.foo())
|
||||
obj.bar = None
|
||||
self.assertIsNone(obj.foo())
|
||||
obj.bar = 'foo'
|
||||
self.assertRaises(AttributeError, obj.foo)
|
||||
|
||||
def test_dt_deserializer(self):
|
||||
dt = timeutils.parse_isotime('1955-11-05T00:00:00Z')
|
||||
self.assertEqual(dt, utils.dt_deserializer(timeutils.isotime(dt)))
|
||||
self.assertIsNone(utils.dt_deserializer(None))
|
||||
self.assertRaises(ValueError, utils.dt_deserializer, 'foo')
|
||||
|
||||
def test_obj_to_primitive_list(self):
|
||||
class MyList(base.ObjectListBase, base.WatcherObject):
|
||||
pass
|
||||
mylist = MyList(self.context)
|
||||
mylist.objects = [1, 2, 3]
|
||||
self.assertEqual([1, 2, 3], base.obj_to_primitive(mylist))
|
||||
|
||||
def test_obj_to_primitive_dict(self):
|
||||
myobj = MyObj(self.context)
|
||||
myobj.foo = 1
|
||||
myobj.bar = 'foo'
|
||||
self.assertEqual({'foo': 1, 'bar': 'foo'},
|
||||
base.obj_to_primitive(myobj))
|
||||
|
||||
def test_obj_to_primitive_recursive(self):
|
||||
class MyList(base.ObjectListBase, base.WatcherObject):
|
||||
pass
|
||||
|
||||
mylist = MyList(self.context)
|
||||
mylist.objects = [MyObj(self.context), MyObj(self.context)]
|
||||
for i, value in enumerate(mylist):
|
||||
value.foo = i
|
||||
self.assertEqual([{'foo': 0}, {'foo': 1}],
|
||||
base.obj_to_primitive(mylist))
|
||||
@contextlib.contextmanager
|
||||
def things_temporarily_local():
|
||||
# Temporarily go non-remote so the conductor handles
|
||||
# this request directly
|
||||
_api = base.WatcherObject.indirection_api
|
||||
base.WatcherObject.indirection_api = None
|
||||
yield
|
||||
base.WatcherObject.indirection_api = _api
|
||||
|
||||
|
||||
class TestObjectListBase(test_base.TestCase):
|
||||
class _TestObject(object):
|
||||
def test_hydration_type_error(self):
|
||||
primitive = {'watcher_object.name': 'MyObj',
|
||||
'watcher_object.namespace': 'watcher',
|
||||
'watcher_object.version': '1.5',
|
||||
'watcher_object.data': {'foo': 'a'}}
|
||||
self.assertRaises(ValueError, MyObj.obj_from_primitive, primitive)
|
||||
|
||||
def test_list_like_operations(self):
|
||||
class Foo(base.ObjectListBase, base.WatcherObject):
|
||||
pass
|
||||
def test_hydration(self):
|
||||
primitive = {'watcher_object.name': 'MyObj',
|
||||
'watcher_object.namespace': 'watcher',
|
||||
'watcher_object.version': '1.5',
|
||||
'watcher_object.data': {'foo': 1}}
|
||||
obj = MyObj.obj_from_primitive(primitive)
|
||||
self.assertEqual(1, obj.foo)
|
||||
|
||||
objlist = Foo(self.context)
|
||||
objlist._context = 'foo'
|
||||
objlist.objects = [1, 2, 3]
|
||||
self.assertEqual(list(objlist), objlist.objects)
|
||||
self.assertEqual(3, len(objlist))
|
||||
self.assertIn(2, objlist)
|
||||
self.assertEqual([1], list(objlist[:1]))
|
||||
self.assertEqual('foo', objlist[:1]._context)
|
||||
self.assertEqual(3, objlist[2])
|
||||
self.assertEqual(1, objlist.count(1))
|
||||
self.assertEqual(1, objlist.index(2))
|
||||
def test_hydration_bad_ns(self):
|
||||
primitive = {'watcher_object.name': 'MyObj',
|
||||
'watcher_object.namespace': 'foo',
|
||||
'watcher_object.version': '1.5',
|
||||
'watcher_object.data': {'foo': 1}}
|
||||
self.assertRaises(object_exception.UnsupportedObjectError,
|
||||
MyObj.obj_from_primitive, primitive)
|
||||
|
||||
def test_serialization(self):
|
||||
class Foo(base.ObjectListBase, base.WatcherObject):
|
||||
pass
|
||||
|
||||
class Bar(base.WatcherObject):
|
||||
fields = {'foo': str}
|
||||
|
||||
obj = Foo(self.context)
|
||||
obj.objects = []
|
||||
for i in 'abc':
|
||||
bar = Bar(self.context)
|
||||
bar.foo = i
|
||||
obj.objects.append(bar)
|
||||
|
||||
obj2 = base.WatcherObject.obj_from_primitive(obj.obj_to_primitive())
|
||||
self.assertFalse(obj is obj2)
|
||||
self.assertEqual([x.foo for x in obj],
|
||||
[y.foo for y in obj2])
|
||||
|
||||
def _test_object_list_version_mappings(self, list_obj_class):
|
||||
# Figure out what sort of object this list is for
|
||||
list_field = list_obj_class.fields['objects']
|
||||
item_obj_field = list_field._type._element_type
|
||||
item_obj_name = item_obj_field._type._obj_name
|
||||
|
||||
# Look through all object classes of this type and make sure that
|
||||
# the versions we find are covered by the parent list class
|
||||
for item_class in base.WatcherObject._obj_classes[item_obj_name]:
|
||||
self.assertIn(
|
||||
item_class.VERSION,
|
||||
list_obj_class.child_versions.values())
|
||||
|
||||
def test_object_version_mappings(self):
|
||||
# Find all object list classes and make sure that they at least handle
|
||||
# all the current object versions
|
||||
for obj_classes in base.WatcherObject._obj_classes.values():
|
||||
for obj_class in obj_classes:
|
||||
if issubclass(obj_class, base.ObjectListBase):
|
||||
self._test_object_list_version_mappings(obj_class)
|
||||
|
||||
def test_list_changes(self):
|
||||
class Foo(base.ObjectListBase, base.WatcherObject):
|
||||
pass
|
||||
|
||||
class Bar(base.WatcherObject):
|
||||
fields = {'foo': str}
|
||||
|
||||
obj = Foo(self.context, objects=[])
|
||||
self.assertEqual(set(['objects']), obj.obj_what_changed())
|
||||
obj.objects.append(Bar(self.context, foo='test'))
|
||||
self.assertEqual(set(['objects']), obj.obj_what_changed())
|
||||
def test_dehydration(self):
|
||||
expected = {'watcher_object.name': 'MyObj',
|
||||
'watcher_object.namespace': 'watcher',
|
||||
'watcher_object.version': '1.5',
|
||||
'watcher_object.data': {'foo': 1}}
|
||||
obj = MyObj(self.context)
|
||||
obj.foo = 1
|
||||
obj.obj_reset_changes()
|
||||
# This should still look dirty because the child is dirty
|
||||
self.assertEqual(set(['objects']), obj.obj_what_changed())
|
||||
obj.objects[0].obj_reset_changes()
|
||||
# This should now look clean because the child is clean
|
||||
self.assertEqual(set(), obj.obj_what_changed())
|
||||
self.assertEqual(expected, obj.obj_to_primitive())
|
||||
|
||||
def test_get_updates(self):
|
||||
obj = MyObj(self.context)
|
||||
self.assertEqual({}, obj.obj_get_changes())
|
||||
obj.foo = 123
|
||||
self.assertEqual({'foo': 123}, obj.obj_get_changes())
|
||||
obj.bar = 'test'
|
||||
self.assertEqual({'foo': 123, 'bar': 'test'}, obj.obj_get_changes())
|
||||
obj.obj_reset_changes()
|
||||
self.assertEqual({}, obj.obj_get_changes())
|
||||
|
||||
def test_object_property(self):
|
||||
obj = MyObj(self.context, foo=1)
|
||||
self.assertEqual(1, obj.foo)
|
||||
|
||||
def test_object_property_type_error(self):
|
||||
obj = MyObj(self.context)
|
||||
|
||||
def fail():
|
||||
obj.foo = 'a'
|
||||
self.assertRaises(ValueError, fail)
|
||||
|
||||
def test_load(self):
|
||||
obj = MyObj(self.context)
|
||||
self.assertEqual('loaded!', obj.bar)
|
||||
|
||||
def test_load_in_base(self):
|
||||
@base.WatcherObjectRegistry.register_if(False)
|
||||
class Foo(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
fields = {'foobar': fields.IntegerField()}
|
||||
obj = Foo(self.context)
|
||||
|
||||
self.assertRaisesRegex(
|
||||
NotImplementedError, "Cannot load 'foobar' in the base class",
|
||||
getattr, obj, 'foobar')
|
||||
|
||||
def test_loaded_in_primitive(self):
|
||||
obj = MyObj(self.context)
|
||||
obj.foo = 1
|
||||
obj.obj_reset_changes()
|
||||
self.assertEqual('loaded!', obj.bar)
|
||||
expected = {'watcher_object.name': 'MyObj',
|
||||
'watcher_object.namespace': 'watcher',
|
||||
'watcher_object.version': '1.5',
|
||||
'watcher_object.changes': ['bar'],
|
||||
'watcher_object.data': {'foo': 1,
|
||||
'bar': 'loaded!'}}
|
||||
self.assertEqual(expected, obj.obj_to_primitive())
|
||||
|
||||
def test_changes_in_primitive(self):
|
||||
obj = MyObj(self.context)
|
||||
obj.foo = 123
|
||||
self.assertEqual(set(['foo']), obj.obj_what_changed())
|
||||
primitive = obj.obj_to_primitive()
|
||||
self.assertIn('watcher_object.changes', primitive)
|
||||
obj2 = MyObj.obj_from_primitive(primitive)
|
||||
self.assertEqual(set(['foo']), obj2.obj_what_changed())
|
||||
obj2.obj_reset_changes()
|
||||
self.assertEqual(set(), obj2.obj_what_changed())
|
||||
|
||||
def test_unknown_objtype(self):
|
||||
self.assertRaises(object_exception.UnsupportedObjectError,
|
||||
base.WatcherObject.obj_class_from_name, 'foo', '1.0')
|
||||
|
||||
def test_with_alternate_context(self):
|
||||
ctxt1 = context.RequestContext('foo', 'foo')
|
||||
ctxt2 = context.RequestContext(user='alternate')
|
||||
obj = MyObj.query(ctxt1)
|
||||
obj.update_test(ctxt2)
|
||||
self.assertEqual('alternate-context', obj.bar)
|
||||
|
||||
def test_orphaned_object(self):
|
||||
obj = MyObj.query(self.context)
|
||||
obj._context = None
|
||||
self.assertRaises(object_exception.OrphanedObjectError,
|
||||
obj.update_test)
|
||||
|
||||
def test_changed_1(self):
|
||||
obj = MyObj.query(self.context)
|
||||
obj.foo = 123
|
||||
self.assertEqual(set(['foo']), obj.obj_what_changed())
|
||||
obj.update_test(self.context)
|
||||
self.assertEqual(set(['foo', 'bar']), obj.obj_what_changed())
|
||||
self.assertEqual(123, obj.foo)
|
||||
|
||||
def test_changed_2(self):
|
||||
obj = MyObj.query(self.context)
|
||||
obj.foo = 123
|
||||
self.assertEqual(set(['foo']), obj.obj_what_changed())
|
||||
obj.save()
|
||||
self.assertEqual(set([]), obj.obj_what_changed())
|
||||
self.assertEqual(123, obj.foo)
|
||||
|
||||
def test_changed_3(self):
|
||||
obj = MyObj.query(self.context)
|
||||
obj.foo = 123
|
||||
self.assertEqual(set(['foo']), obj.obj_what_changed())
|
||||
obj.refresh()
|
||||
self.assertEqual(set([]), obj.obj_what_changed())
|
||||
self.assertEqual(321, obj.foo)
|
||||
self.assertEqual('refreshed', obj.bar)
|
||||
|
||||
def test_changed_4(self):
|
||||
obj = MyObj.query(self.context)
|
||||
obj.bar = 'something'
|
||||
self.assertEqual(set(['bar']), obj.obj_what_changed())
|
||||
obj.modify_save_modify(self.context)
|
||||
self.assertEqual(set(['foo']), obj.obj_what_changed())
|
||||
self.assertEqual(42, obj.foo)
|
||||
self.assertEqual('meow', obj.bar)
|
||||
|
||||
def test_static_result(self):
|
||||
obj = MyObj.query(self.context)
|
||||
self.assertEqual('bar', obj.bar)
|
||||
result = obj.marco()
|
||||
self.assertEqual('polo', result)
|
||||
|
||||
def test_updates(self):
|
||||
obj = MyObj.query(self.context)
|
||||
self.assertEqual(1, obj.foo)
|
||||
obj.update_test()
|
||||
self.assertEqual('updated', obj.bar)
|
||||
|
||||
def test_base_attributes(self):
|
||||
dt = datetime.datetime(1955, 11, 5, 0, 0, tzinfo=iso8601.iso8601.Utc())
|
||||
datatime = fields.DateTimeField()
|
||||
obj = MyObj(self.context)
|
||||
obj.created_at = dt
|
||||
obj.updated_at = dt
|
||||
expected = {'watcher_object.name': 'MyObj',
|
||||
'watcher_object.namespace': 'watcher',
|
||||
'watcher_object.version': '1.5',
|
||||
'watcher_object.changes':
|
||||
['created_at', 'updated_at'],
|
||||
'watcher_object.data':
|
||||
{'created_at': datatime.stringify(dt),
|
||||
'updated_at': datatime.stringify(dt),
|
||||
}
|
||||
}
|
||||
actual = obj.obj_to_primitive()
|
||||
# watcher_object.changes is built from a set and order is undefined
|
||||
self.assertEqual(sorted(expected['watcher_object.changes']),
|
||||
sorted(actual['watcher_object.changes']))
|
||||
del expected[
|
||||
'watcher_object.changes'], actual['watcher_object.changes']
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_contains(self):
|
||||
obj = MyObj(self.context)
|
||||
self.assertNotIn('foo', obj)
|
||||
obj.foo = 1
|
||||
self.assertIn('foo', obj)
|
||||
self.assertNotIn('does_not_exist', obj)
|
||||
|
||||
def test_obj_attr_is_set(self):
|
||||
obj = MyObj(self.context, foo=1)
|
||||
self.assertTrue(obj.obj_attr_is_set('foo'))
|
||||
self.assertFalse(obj.obj_attr_is_set('bar'))
|
||||
self.assertRaises(AttributeError, obj.obj_attr_is_set, 'bang')
|
||||
|
||||
def test_get(self):
|
||||
obj = MyObj(self.context, foo=1)
|
||||
# Foo has value, should not get the default
|
||||
self.assertEqual(obj.get('foo', 2), 1)
|
||||
# Foo has value, should return the value without error
|
||||
self.assertEqual(obj.get('foo'), 1)
|
||||
# Bar is not loaded, so we should get the default
|
||||
self.assertEqual(obj.get('bar', 'not-loaded'), 'not-loaded')
|
||||
# Bar without a default should lazy-load
|
||||
self.assertEqual(obj.get('bar'), 'loaded!')
|
||||
# Bar now has a default, but loaded value should be returned
|
||||
self.assertEqual(obj.get('bar', 'not-loaded'), 'loaded!')
|
||||
# Invalid attribute should raise AttributeError
|
||||
self.assertRaises(AttributeError, obj.get, 'nothing')
|
||||
# ...even with a default
|
||||
self.assertRaises(AttributeError, obj.get, 'nothing', 3)
|
||||
|
||||
def test_object_inheritance(self):
|
||||
base_fields = (
|
||||
list(base.WatcherObject.fields) +
|
||||
list(base.WatcherPersistentObject.fields))
|
||||
myobj_fields = ['foo', 'bar', 'missing'] + base_fields
|
||||
myobj3_fields = ['new_field']
|
||||
self.assertTrue(issubclass(WatcherTestSubclassedObject, MyObj))
|
||||
self.assertEqual(len(myobj_fields), len(MyObj.fields))
|
||||
self.assertEqual(set(myobj_fields), set(MyObj.fields.keys()))
|
||||
self.assertEqual(len(myobj_fields) + len(myobj3_fields),
|
||||
len(WatcherTestSubclassedObject.fields))
|
||||
self.assertEqual(set(myobj_fields) | set(myobj3_fields),
|
||||
set(WatcherTestSubclassedObject.fields.keys()))
|
||||
|
||||
def test_get_changes(self):
|
||||
obj = MyObj(self.context)
|
||||
self.assertEqual({}, obj.obj_get_changes())
|
||||
obj.foo = 123
|
||||
self.assertEqual({'foo': 123}, obj.obj_get_changes())
|
||||
obj.bar = 'test'
|
||||
self.assertEqual({'foo': 123, 'bar': 'test'}, obj.obj_get_changes())
|
||||
obj.obj_reset_changes()
|
||||
self.assertEqual({}, obj.obj_get_changes())
|
||||
|
||||
def test_obj_fields(self):
|
||||
@base.WatcherObjectRegistry.register_if(False)
|
||||
class TestObj(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
fields = {'foo': fields.IntegerField()}
|
||||
obj_extra_fields = ['bar']
|
||||
|
||||
@property
|
||||
def bar(self):
|
||||
return 'this is bar'
|
||||
|
||||
obj = TestObj(self.context)
|
||||
self.assertEqual(set(['created_at', 'updated_at', 'deleted_at',
|
||||
'foo', 'bar']),
|
||||
set(obj.obj_fields))
|
||||
|
||||
def test_refresh_object(self):
|
||||
@base.WatcherObjectRegistry.register_if(False)
|
||||
class TestObj(base.WatcherPersistentObject, base.WatcherObject,
|
||||
base.WatcherObjectDictCompat):
|
||||
fields = {'foo': fields.IntegerField(),
|
||||
'bar': fields.StringField()}
|
||||
|
||||
obj = TestObj(self.context)
|
||||
current_obj = TestObj(self.context)
|
||||
obj.foo = 10
|
||||
obj.bar = 'obj.bar'
|
||||
current_obj.foo = 2
|
||||
current_obj.bar = 'current.bar'
|
||||
obj.obj_refresh(current_obj)
|
||||
self.assertEqual(obj.foo, 2)
|
||||
self.assertEqual(obj.bar, 'current.bar')
|
||||
|
||||
def test_obj_constructor(self):
|
||||
obj = MyObj(self.context, foo=123, bar='abc')
|
||||
self.assertEqual(123, obj.foo)
|
||||
self.assertEqual('abc', obj.bar)
|
||||
self.assertEqual(set(['foo', 'bar']), obj.obj_what_changed())
|
||||
|
||||
def test_assign_value_without_DictCompat(self):
|
||||
class TestObj(base.WatcherObject):
|
||||
fields = {'foo': fields.IntegerField(),
|
||||
'bar': fields.StringField()}
|
||||
obj = TestObj(self.context)
|
||||
obj.foo = 10
|
||||
err_message = ''
|
||||
try:
|
||||
obj['bar'] = 'value'
|
||||
except TypeError as e:
|
||||
err_message = six.text_type(e)
|
||||
finally:
|
||||
self.assertIn("'TestObj' object does not support item assignment",
|
||||
err_message)
|
||||
|
||||
|
||||
class TestObject(_LocalTest, _TestObject):
|
||||
pass
|
||||
|
||||
|
||||
# The hashes are help developers to check if the change of objects need a
|
||||
# version bump. It is md5 hash of object fields and remotable methods.
|
||||
# The fingerprint values should only be changed if there is a version bump.
|
||||
expected_object_fingerprints = {
|
||||
'Goal': '1.0-93881622db05e7b67a65ca885b4a022e',
|
||||
'Strategy': '1.0-e60f62cc854c6e63fb1c3befbfc8629e',
|
||||
'AuditTemplate': '1.0-7432ee4d3ce0c7cbb9d11a4565ee8eb6',
|
||||
'Audit': '1.0-ebfc5360d019baf583a10a8a27071c97',
|
||||
'ActionPlan': '1.0-cc76fd7f0e8479aeff817dd266341de4',
|
||||
'Action': '1.0-a78f69c0da98e13e601f9646f6b2f883',
|
||||
'EfficacyIndicator': '1.0-655b71234a82bc7478aff964639c4bb0',
|
||||
'ScoringEngine': '1.0-4abbe833544000728e17bd9e83f97576',
|
||||
'Service': '1.0-4b35b99ada9677a882c9de2b30212f35',
|
||||
'MyObj': '1.5-23c516d1e842f365f694e688d34e47c3',
|
||||
}
|
||||
|
||||
|
||||
class TestObjectVersions(test_base.TestCase):
|
||||
|
||||
def test_object_version_check(self):
|
||||
classes = base.WatcherObjectRegistry.obj_classes()
|
||||
checker = object_fixture.ObjectVersionChecker(obj_classes=classes)
|
||||
# Compute the difference between actual fingerprints and
|
||||
# expect fingerprints. expect = actual = {} if there is no change.
|
||||
expect, actual = checker.test_hashes(expected_object_fingerprints)
|
||||
self.assertEqual(expect, actual,
|
||||
"Some objects fields or remotable methods have been "
|
||||
"modified. Please make sure the version of those "
|
||||
"objects have been bumped and then update "
|
||||
"expected_object_fingerprints with the new hashes. ")
|
||||
|
||||
|
||||
class TestObjectSerializer(test_base.TestCase):
|
||||
|
||||
def test_serialize_entity_primitive(self):
|
||||
ser = base.WatcherObjectSerializer()
|
||||
for thing in (1, 'foo', [1, 2], {'foo': 'bar'}):
|
||||
self.assertEqual(thing, ser.serialize_entity(None, thing))
|
||||
|
||||
def test_deserialize_entity_primitive(self):
|
||||
ser = base.WatcherObjectSerializer()
|
||||
for thing in (1, 'foo', [1, 2], {'foo': 'bar'}):
|
||||
self.assertEqual(thing, ser.deserialize_entity(None, thing))
|
||||
|
||||
def test_object_serialization(self):
|
||||
ser = base.WatcherObjectSerializer()
|
||||
obj = MyObj(self.context)
|
||||
@@ -321,3 +461,83 @@ class TestObjectSerializer(test_base.TestCase):
|
||||
self.assertEqual(1, len(thing2))
|
||||
for item in thing2:
|
||||
self.assertIsInstance(item, MyObj)
|
||||
|
||||
@mock.patch('watcher.objects.base.WatcherObject.indirection_api')
|
||||
def _test_deserialize_entity_newer(self, obj_version, backported_to,
|
||||
mock_indirection_api,
|
||||
my_version='1.6'):
|
||||
ser = base.WatcherObjectSerializer()
|
||||
mock_indirection_api.object_backport_versions.return_value \
|
||||
= 'backported'
|
||||
|
||||
@base.WatcherObjectRegistry.register
|
||||
class MyTestObj(MyObj):
|
||||
VERSION = my_version
|
||||
|
||||
obj = MyTestObj(self.context)
|
||||
obj.VERSION = obj_version
|
||||
primitive = obj.obj_to_primitive()
|
||||
result = ser.deserialize_entity(self.context, primitive)
|
||||
if backported_to is None:
|
||||
self.assertFalse(
|
||||
mock_indirection_api.object_backport_versions.called)
|
||||
else:
|
||||
self.assertEqual('backported', result)
|
||||
versions = object_base.obj_tree_get_versions('MyTestObj')
|
||||
mock_indirection_api.object_backport_versions.assert_called_with(
|
||||
self.context, primitive, versions)
|
||||
|
||||
def test_deserialize_entity_newer_version_backports(self):
|
||||
"Test object with unsupported (newer) version"
|
||||
self._test_deserialize_entity_newer('1.25', '1.6')
|
||||
|
||||
def test_deserialize_entity_same_revision_does_not_backport(self):
|
||||
"Test object with supported revision"
|
||||
self._test_deserialize_entity_newer('1.6', None)
|
||||
|
||||
def test_deserialize_entity_newer_revision_does_not_backport_zero(self):
|
||||
"Test object with supported revision"
|
||||
self._test_deserialize_entity_newer('1.6.0', None)
|
||||
|
||||
def test_deserialize_entity_newer_revision_does_not_backport(self):
|
||||
"Test object with supported (newer) revision"
|
||||
self._test_deserialize_entity_newer('1.6.1', None)
|
||||
|
||||
def test_deserialize_entity_newer_version_passes_revision(self):
|
||||
"Test object with unsupported (newer) version and revision"
|
||||
self._test_deserialize_entity_newer('1.7', '1.6.1', my_version='1.6.1')
|
||||
|
||||
|
||||
class TestRegistry(test_base.TestCase):
|
||||
|
||||
@mock.patch('watcher.objects.base.objects')
|
||||
def test_hook_chooses_newer_properly(self, mock_objects):
|
||||
reg = base.WatcherObjectRegistry()
|
||||
reg.registration_hook(MyObj, 0)
|
||||
|
||||
class MyNewerObj(object):
|
||||
VERSION = '1.123'
|
||||
|
||||
@classmethod
|
||||
def obj_name(cls):
|
||||
return 'MyObj'
|
||||
|
||||
self.assertEqual(MyObj, mock_objects.MyObj)
|
||||
reg.registration_hook(MyNewerObj, 0)
|
||||
self.assertEqual(MyNewerObj, mock_objects.MyObj)
|
||||
|
||||
@mock.patch('watcher.objects.base.objects')
|
||||
def test_hook_keeps_newer_properly(self, mock_objects):
|
||||
reg = base.WatcherObjectRegistry()
|
||||
reg.registration_hook(MyObj, 0)
|
||||
|
||||
class MyOlderObj(object):
|
||||
VERSION = '1.1'
|
||||
|
||||
@classmethod
|
||||
def obj_name(cls):
|
||||
return 'MyObj'
|
||||
|
||||
self.assertEqual(MyObj, mock_objects.MyObj)
|
||||
reg.registration_hook(MyOlderObj, 0)
|
||||
self.assertEqual(MyObj, mock_objects.MyObj)
|
||||
|
@@ -14,7 +14,6 @@
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from testtools import matchers
|
||||
|
||||
from watcher import objects
|
||||
from watcher.tests.db import base
|
||||
@@ -43,7 +42,7 @@ class TestServiceObject(base.DbTestCase):
|
||||
mock_get_list.return_value = [self.fake_service]
|
||||
services = objects.Service.list(self.context)
|
||||
self.assertEqual(1, mock_get_list.call_count, 1)
|
||||
self.assertThat(services, matchers.HasLength(1))
|
||||
self.assertEqual(1, len(services))
|
||||
self.assertIsInstance(services[0], objects.Service)
|
||||
self.assertEqual(self.context, services[0]._context)
|
||||
|
||||
|
Reference in New Issue
Block a user