From 2a97b665dd70f3051a351e22edd6be485abde899 Mon Sep 17 00:00:00 2001 From: manasdk Date: Wed, 6 Aug 2014 09:56:52 -0700 Subject: [PATCH] Rename #1 --- st2client/st2client/models/reactor.py | 2 +- st2common/st2common/models/api/reactor.py | 98 ++++++----- st2common/st2common/models/base.py | 4 +- st2common/st2common/models/db/reactor.py | 53 +++--- st2common/st2common/persistence/reactor.py | 17 +- .../st2reactorcontroller/controllers/root.py | 16 +- .../st2reactorcontroller/controllers/rules.py | 5 +- .../controllers/triggers.py | 152 ++++++++++++++++-- 8 files changed, 245 insertions(+), 102 deletions(-) diff --git a/st2client/st2client/models/reactor.py b/st2client/st2client/models/reactor.py index 82b7c696143..2a0a5a961bc 100644 --- a/st2client/st2client/models/reactor.py +++ b/st2client/st2client/models/reactor.py @@ -7,7 +7,7 @@ class Trigger(core.Resource): - _plural = 'Triggers' + _plural = 'Triggertypes' class Rule(core.Resource): diff --git a/st2common/st2common/models/api/reactor.py b/st2common/st2common/models/api/reactor.py index 0b076b19d21..5609b35230e 100644 --- a/st2common/st2common/models/api/reactor.py +++ b/st2common/st2common/models/api/reactor.py @@ -1,72 +1,90 @@ import datetime +import uuid from wsme import types as wstypes from st2common.models.base import BaseAPI from st2common.models.api.stormbase import StormBaseAPI, StormFoundationAPI -from st2common.models.db.reactor import RuleDB, ActionExecutionSpecDB, TriggerDB, AHTriggerDB +from st2common.models.db.reactor import RuleDB, ActionExecutionSpecDB, TriggerTypeDB, TriggerDB import st2common.validators.api.reactor as validator -def get_id(identifiable): - if identifiable is None: - return '' - return str(identifiable.id) - - -def get_ref(identifiable): - if identifiable is None: - return {} - return {'id': str(identifiable.id), 'name': identifiable.name} - - -def get_model_from_ref(db_api, ref): - if ref is None: - return None - model_id = ref['id'] if 'id' in ref else None - if model_id is not None: - return db_api.get_by_id(model_id) - model_name = ref['name'] if 'name' in ref else None - for model in db_api.query(name=model_name): - return model - return None - - -class TriggerAPI(BaseAPI): +class TriggerTypeAPI(BaseAPI): schema = { - "type": "object" + 'type': 'object', + 'properties': { + 'id': { + 'type': 'string', + 'default': None + }, + 'name': { + 'type': 'string' + }, + 'description': { + 'type': 'string', + 'default': None + }, + 'payload_info': { + 'type': 'array' + }, + 'parameters_schema': { + 'type': 'object' + } + }, + 'required': ['name'], + 'additionalProperties': False } @classmethod def from_model(cls, model): trigger = model.to_mongo() - trigger['name'] = str(trigger.pop('_id')) + trigger['id'] = str(trigger.pop('_id')) return cls(**trigger) @classmethod - def to_model(cls, trigger): - model = StormBaseAPI.to_model(TriggerDB, trigger) - model.payload_info = trigger.payload_info + def to_model(cls, triggertype): + model = StormBaseAPI.to_model(TriggerTypeDB, triggertype) + model.payload_info = triggertype.payload_info + model.parameters_schema = triggertype.parameters_schema return model -class AHTriggerAPI(BaseAPI): +class TriggerAPI(BaseAPI): schema = { - "type": "object" + 'type': 'object', + 'properties': { + 'id': { + 'type': 'string', + 'default': None + }, + 'name': { + 'type': 'string' + }, + 'type': { + 'type': 'string' + }, + 'parameters': { + 'type': 'object' + } + }, + 'required': ['type'], + 'additionalProperties': False } @classmethod def from_model(cls, model): trigger = model.to_mongo() - trigger['name'] = str(trigger.pop('_id')) - if trigger.has_key('type'): - trigger['type'] = str(trigger.pop('type')) + trigger['id'] = str(trigger.pop('_id')) + if 'type' in trigger: + trigger['type'] = str(trigger.pop('type').get('name', '')) return cls(**trigger) @classmethod def to_model(cls, trigger): - model = StormFoundationAPI.to_model(AHTriggerDB, trigger) - model.name = trigger.name - model.type = getattr(trigger, 'type', None) + model = StormFoundationAPI.to_model(TriggerDB, trigger) + # assign a name if none is provided. + model.name = str(uuid.uuid4()) if model.name is None or len(model.name) < 1 \ + else trigger.name + model.type = {'name': getattr(trigger, 'type', None)} model.parameters = getattr(trigger, 'parameters', None) return model @@ -79,7 +97,7 @@ class TriggerInstanceAPI(StormFoundationAPI): @classmethod def from_model(kls, model): trigger_instance = StormFoundationAPI.from_model(kls, model) - trigger_instance.trigger = model.trigger + trigger_instance.trigger = model.trigger.get('name', '') trigger_instance.payload = dict(model.payload) trigger_instance.occurrence_time = model.occurrence_time return trigger_instance diff --git a/st2common/st2common/models/base.py b/st2common/st2common/models/base.py index 6b7863a7753..9afb232f978 100644 --- a/st2common/st2common/models/base.py +++ b/st2common/st2common/models/base.py @@ -26,7 +26,7 @@ def set_defaults(validator, properties, instance, schema): instance.setdefault(property, subschema["default"]) return jsonschema.validators.extend( - validator_class, {"properties" : set_defaults}, + validator_class, {"properties": set_defaults}, ) @@ -100,4 +100,4 @@ def callfunction(*args, **kwargs): return callfunction - return decorate \ No newline at end of file + return decorate diff --git a/st2common/st2common/models/db/reactor.py b/st2common/st2common/models/db/reactor.py index 902fdd48e4e..c4406ac657d 100644 --- a/st2common/st2common/models/db/reactor.py +++ b/st2common/st2common/models/db/reactor.py @@ -3,22 +3,7 @@ from st2common.models.db.stormbase import StormBaseDB, StormFoundationDB -class TriggerSourceDB(StormBaseDB): - """Source of a trigger. Typically an external system or service that - generates events which must be adapted to a trigger using the provided - adapter. - Attribute: - url: url of the source - auth_token: token used by an adapter to authenticate with the - adapter_file_uri: uri of the adapter which will translate an event - specific to the source to a corresponding trigger. - """ - url = me.URLField() - auth_token = me.StringField() - adapter_file_uri = me.StringField() - - -class TriggerDB(me.Document): +class TriggerTypeDB(StormBaseDB): """Description of a specific kind/type of a trigger. The name is expected uniquely identify a trigger in the namespace of all triggers provided by a specific trigger_source. @@ -26,17 +11,36 @@ class TriggerDB(me.Document): trigger_source: Source that owns this trigger type. payload_info: Meta information of the expected payload. """ - name = me.StringField(primary_key=True) - description = me.StringField() payload_info = me.ListField() parameters_schema = me.DictField() + def __str__(self): + result = [] + result.append('TriggerTypeDB@') + result.append(str(id(self))) + result.append('(') + result.append('id="%s", ' % self.id) + result.append('name"%s", ' % self.name) + result.append('payload_info="%s", ' % self.payload_info) + result.append('parameters_schema=%s)' % str(self.parameters_schema)) + return ''.join(result) + -class AHTriggerDB(me.Document): - name = me.StringField(primary_key=True) - type = me.ReferenceField(TriggerDB.__name__) +class TriggerDB(StormBaseDB): + type = me.DictField() parameters = me.DictField() + def __str__(self): + result = [] + result.append('TriggerDB@') + result.append(str(id(self))) + result.append('(') + result.append('id="%s", ' % self.id) + result.append('name"%s", ' % self.name) + result.append('type="%s", ' % self.type) + result.append('parameters=%s)' % str(self.parameters)) + return ''.join(result) + class TriggerInstanceDB(StormFoundationDB): """An instance or occurrence of a type of Trigger. @@ -45,7 +49,7 @@ class TriggerInstanceDB(StormFoundationDB): payload (dict): payload specific to the occurrence. occurrence_time (datetime): time of occurrence of the trigger. """ - trigger = me.ReferenceField(AHTriggerDB.__name__) + trigger = me.DictField() payload = me.DictField() occurrence_time = me.DateTimeField() @@ -66,7 +70,7 @@ class RuleDB(StormBaseDB): status: enabled or disabled. If disabled occurrence of the trigger does not lead to execution of a action and vice-versa. """ - trigger = me.ReferenceField(AHTriggerDB) + trigger = me.DictField() criteria = me.DictField() action = me.EmbeddedDocumentField(ActionExecutionSpecDB) enabled = me.BooleanField(required=True, default=True, @@ -88,9 +92,8 @@ class RuleEnforcementDB(StormFoundationDB): # specialized access objects -triggersource_access = MongoDBAccess(TriggerSourceDB) +triggertype_access = MongoDBAccess(TriggerTypeDB) trigger_access = MongoDBAccess(TriggerDB) -ahtrigger_access = MongoDBAccess(AHTriggerDB) triggerinstance_access = MongoDBAccess(TriggerInstanceDB) rule_access = MongoDBAccess(RuleDB) ruleenforcement_access = MongoDBAccess(RuleEnforcementDB) diff --git a/st2common/st2common/persistence/reactor.py b/st2common/st2common/persistence/reactor.py index fa5850b3ffd..1c8daf256e1 100644 --- a/st2common/st2common/persistence/reactor.py +++ b/st2common/st2common/persistence/reactor.py @@ -1,11 +1,10 @@ from st2common.persistence import Access -from st2common.models.db.reactor import triggersource_access, \ - trigger_access, triggerinstance_access, rule_access, \ - ruleenforcement_access, ahtrigger_access +from st2common.models.db.reactor import triggertype_access, trigger_access, triggerinstance_access,\ + rule_access, ruleenforcement_access -class TriggerSource(Access): - IMPL = triggersource_access +class TriggerType(Access): + IMPL = triggertype_access @classmethod def _get_impl(kls): @@ -20,14 +19,6 @@ def _get_impl(kls): return kls.IMPL -class AHTrigger(Access): - IMPL = ahtrigger_access - - @classmethod - def _get_impl(kls): - return kls.IMPL - - class TriggerInstance(Access): IMPL = triggerinstance_access diff --git a/st2reactorcontroller/st2reactorcontroller/controllers/root.py b/st2reactorcontroller/st2reactorcontroller/controllers/root.py index c0616c3c8f2..384f5f7d751 100644 --- a/st2reactorcontroller/st2reactorcontroller/controllers/root.py +++ b/st2reactorcontroller/st2reactorcontroller/controllers/root.py @@ -1,18 +1,18 @@ from pecan import expose from webob.exc import status_map -import st2reactorcontroller.controllers.triggers -import st2reactorcontroller.controllers.rules +from st2reactorcontroller.controllers.triggers import TriggerTypeController, TriggerController, \ + TriggerInstanceController +from st2reactorcontroller.controllers.rules import RuleController, RuleEnforcementController class RootController(object): - triggers = st2reactorcontroller.controllers.triggers.TriggerController() - triggerinstances = \ - st2reactorcontroller.controllers.triggers.TriggerInstanceController() - rules = st2reactorcontroller.controllers.rules.RuleController() - ruleenforcements = \ - st2reactorcontroller.controllers.rules.RuleEnforcementController() + triggertypes = TriggerTypeController() + triggers = TriggerController() + triggerinstances = TriggerInstanceController() + rules = RuleController() + ruleenforcements = RuleEnforcementController() @expose(generic=True, template='index.html') def index(self): diff --git a/st2reactorcontroller/st2reactorcontroller/controllers/rules.py b/st2reactorcontroller/st2reactorcontroller/controllers/rules.py index d3877d56bb2..33a5092828e 100644 --- a/st2reactorcontroller/st2reactorcontroller/controllers/rules.py +++ b/st2reactorcontroller/st2reactorcontroller/controllers/rules.py @@ -5,10 +5,10 @@ from pecan.rest import RestController from st2common import log as logging from st2common.exceptions.apivalidation import ValueValidationException -from st2common.models.api.reactor import RuleAPI, RuleEnforcementAPI, AHTriggerAPI +from st2common.models.api.reactor import RuleAPI, RuleEnforcementAPI, TriggerAPI from st2common.models.db.reactor import RuleDB from st2common.models.base import jsexpose -from st2common.persistence.reactor import Rule, RuleEnforcement, AHTrigger, Trigger +from st2common.persistence.reactor import Rule, RuleEnforcement, TriggerType, Trigger from wsme import types as wstypes LOG = logging.getLogger(__name__) @@ -47,7 +47,6 @@ def get_all(self, name=None): LOG.debug('GET all /rules/ client_result=%s', rule_apis) return rule_apis - @jsexpose(body=RuleAPI, status_code=httplib.CREATED) def post(self, rule): """ diff --git a/st2reactorcontroller/st2reactorcontroller/controllers/triggers.py b/st2reactorcontroller/st2reactorcontroller/controllers/triggers.py index 77b0f8940f4..45bb21c0451 100644 --- a/st2reactorcontroller/st2reactorcontroller/controllers/triggers.py +++ b/st2reactorcontroller/st2reactorcontroller/controllers/triggers.py @@ -4,27 +4,159 @@ from pecan import abort from pecan.rest import RestController from st2common import log as logging -from st2common.models.api.reactor import TriggerAPI, TriggerInstanceAPI +from st2common.models.api.reactor import TriggerTypeAPI, TriggerAPI, TriggerInstanceAPI from st2common.models.base import jsexpose -from st2common.persistence.reactor import Trigger, TriggerInstance +from st2common.persistence.reactor import TriggerType, Trigger, TriggerInstance from wsme import types as wstypes LOG = logging.getLogger(__name__) +class TriggerTypeController(RestController): + """ + Implements the RESTful web endpoint that handles + the lifecycle of TriggerTypes in the system. + """ + # @wsme_pecan.wsexpose(TriggerTypeAPI, wstypes.text) + @jsexpose(str) + def get_one(self, triggertype_id): + + """ + List triggertypes by id. + + Handle: + GET /triggertypes/1 + """ + LOG.info('GET /triggertypes/ with id=%s', id) + triggertype_db = TriggerTypeController.__get_by_id(triggertype_id) + triggertype_api = TriggerTypeAPI.from_model(triggertype_db) + LOG.debug('GET /triggertypes/ with id=%s, client_result=%s', id, triggertype_api) + return triggertype_api + + @jsexpose(str) + def get_all(self, name=None): + """ + List all triggertypes. + + Handles requests: + GET /triggertypes/ + """ + LOG.info('GET all /triggertypes/ and name=%s', name) + triggertype_dbs = TriggerType.get_all() if name is None else \ + TriggerTypeController.__get_by_name(name) + triggertype_apis = [TriggerTypeAPI.from_model(triggertype_db) for triggertype_db in + triggertype_dbs] + LOG.debug('GET all /triggertypes/ client_result=%s', triggertype_apis) + return triggertype_apis + + # @wsme_pecan.wsexpose(TriggerTypeAPI, body=TriggerTypeAPI, status_code=httplib.CREATED) + @jsexpose(body=TriggerTypeAPI, status_code=httplib.CREATED) + def post(self, triggertype): + """ + Create a new triggertype. + + Handles requests: + POST /triggertypes/ + """ + LOG.info('POST /triggertypes/ with triggertype data=%s', triggertype) + + try: + triggertype_db = TriggerTypeAPI.to_model(triggertype) + LOG.debug('/triggertypes/ POST verified TriggerTypeAPI and formulated TriggerTypeDB=%s', + triggertype_db) + triggertype_db = TriggerType.add_or_update(triggertype_db) + except (ValidationError, ValueError) as e: + LOG.exception('Validation failed for triggertype data=%s.', triggertype) + abort(httplib.BAD_REQUEST, str(e)) + except NotUniqueError as e: + LOG.exception('TriggerType creation of %s failed with uniqueness conflict.', + triggertype) + abort(httplib.CONFLICT, str(e)) + + LOG.debug('/triggertypes/ POST saved TriggerTypeDB object=%s', triggertype_db) + triggertype_api = TriggerTypeAPI.from_model(triggertype_db) + LOG.debug('POST /triggertypes/ client_result=%s', triggertype_api) + + return triggertype_api + + # @wsme_pecan.wsexpose(TriggerTypeAPI, wstypes.text, body=TriggerTypeAPI, + # status_code=httplib.OK) + @jsexpose(str, body=TriggerTypeAPI, status_code=httplib.CREATED) + def put(self, triggertype_id, triggertype): + LOG.info('PUT /triggertypes/ with triggertype id=%s and data=%s', triggertype_id, + triggertype) + triggertype_db = TriggerTypeController.__get_by_id(triggertype_id) + LOG.debug('PUT /triggertypes/ lookup with id=%s found object: %s', triggertype_id, + triggertype_db) + + try: + triggertype_db = TriggerTypeAPI.to_model(triggertype) + if triggertype.id is not None and len(triggertype.id) > 0 and \ + triggertype.id != triggertype_id: + LOG.warning('Discarding mismatched id=%s found in payload and using uri_id=%s.', + triggertype.id, triggertype_id) + triggertype_db.id = triggertype_id + triggertype_db = TriggerType.add_or_update(triggertype_db) + LOG.debug('/triggertypes/ PUT updated TriggerTypeDB object=%s', triggertype_db) + except (ValidationError, ValueError) as e: + LOG.exception('Validation failed for triggertype data=%s', triggertype) + abort(httplib.BAD_REQUEST, str(e)) + + triggertype_api = TriggerTypeAPI.from_model(triggertype_db) + LOG.debug('PUT /triggertypes/ client_result=%s', triggertype_api) + + return triggertype_api + + # @wsme_pecan.wsexpose(None, wstypes.text, status_code=httplib.NO_CONTENT) + @jsexpose(str, status_code=httplib.NO_CONTENT) + def delete(self, triggertype_id): + """ + Delete a triggertype. + + Handles requests: + DELETE /triggertypes/1 + """ + LOG.info('DELETE /triggertypes/ with id=%s', triggertype_id) + triggertype_db = TriggerTypeController.__get_by_id(triggertype_id) + LOG.debug('DELETE /triggertypes/ lookup with id=%s found object: %s', triggertype_id, + triggertype_db) + try: + TriggerType.delete(triggertype_db) + except Exception: + LOG.exception('Database delete encountered exception during delete of id="%s". ', + triggertype_id) + + @staticmethod + def __get_by_id(triggertype_id): + try: + return TriggerType.get_by_id(triggertype_id) + except (ValueError, ValidationError): + LOG.exception('Database lookup for id="%s" resulted in exception.', triggertype_id) + abort(httplib.NOT_FOUND) + + @staticmethod + def __get_by_name(triggertype_name): + try: + return [TriggerType.get_by_name(triggertype_name)] + except ValueError as e: + LOG.debug('Database lookup for name="%s" resulted in exception : %s.', + triggertype_name, e) + return [] + + class TriggerController(RestController): """ Implements the RESTful web endpoint that handles the lifecycle of Triggers in the system. """ - # @wsme_pecan.wsexpose(TriggerAPI, wstypes.text) + @jsexpose(str) def get_one(self, trigger_id): """ - List triggers by id. + List triggertypes by id. Handle: - GET /triggers/1 + GET /triggertypes/1 """ LOG.info('GET /triggers/ with id=%s', id) trigger_db = TriggerController.__get_by_id(trigger_id) @@ -32,7 +164,7 @@ def get_one(self, trigger_id): LOG.debug('GET /triggers/ with id=%s, client_result=%s', id, trigger_api) return trigger_api - # @wsme_pecan.wsexpose([TriggerAPI], wstypes.text) + @jsexpose(str) def get_all(self, name=None): """ List all triggers. @@ -46,7 +178,6 @@ def get_all(self, name=None): LOG.debug('GET all /triggers/ client_result=%s', trigger_apis) return trigger_apis - # @wsme_pecan.wsexpose(TriggerAPI, body=TriggerAPI, status_code=httplib.CREATED) @jsexpose(body=TriggerAPI, status_code=httplib.CREATED) def post(self, trigger): """ @@ -74,7 +205,7 @@ def post(self, trigger): return trigger_api - # @wsme_pecan.wsexpose(TriggerAPI, wstypes.text, body=TriggerAPI, status_code=httplib.OK) + @jsexpose(str, body=TriggerAPI, status_code=httplib.OK) def put(self, trigger_id, trigger): LOG.info('PUT /triggers/ with trigger id=%s and data=%s', trigger_id, trigger) trigger_db = TriggerController.__get_by_id(trigger_id) @@ -97,7 +228,7 @@ def put(self, trigger_id, trigger): return trigger_api - # @wsme_pecan.wsexpose(None, wstypes.text, status_code=httplib.NO_CONTENT) + @jsexpose(str, status_code=httplib.NO_CONTENT) def delete(self, trigger_id): """ Delete a trigger. @@ -111,7 +242,8 @@ def delete(self, trigger_id): try: Trigger.delete(trigger_db) except Exception: - LOG.exception('Database delete encountered exception during delete of id="%s". ', trigger_id) + LOG.exception('Database delete encountered exception during delete of id="%s". ', + trigger_id) @staticmethod def __get_by_id(trigger_id):