Skip to content

Commit

Permalink
Merge pull request #31 from diggyk/master
Browse files Browse the repository at this point in the history
Changed the schema for Fates and how they work
  • Loading branch information
gmjosack committed Aug 26, 2015
2 parents 1b4cbeb + cd748fb commit 683f8b7
Show file tree
Hide file tree
Showing 13 changed files with 344 additions and 138 deletions.
5 changes: 3 additions & 2 deletions db/bootstrap.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ UNLOCK TABLES;

LOCK TABLES `fates` WRITE;

INSERT INTO `fates` (`id`, `creation_type_id`, `completion_type_id`, `intermediate`, `description`)
INSERT INTO `fates` (`id`, `creation_type_id`, `completion_type_id`, `follows`, `description`)
VALUES
(1,1,2, 0, 'A system that needs a reboot can be cleared by rebooting the machine.'),
(2,3,4, 0, 'A system that needs maintenance made ready before maintenance can occur.'),
(3,4,5, 1, 'Maintenance must be performed on a system that is prepped.');
(3,4,5, 2, 'Maintenance must be performed on a system that is prepped.'),
(4,1,4, 1, 'A system that needs a reboot can be released instead.');

UNLOCK TABLES;
50 changes: 18 additions & 32 deletions hermes/handlers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1163,8 +1163,8 @@ def post(self):
{
"creationEventTypeId": 1,
"completionEventTypeId": 2,
"intermediate": false,
"description": "This is a fate"
"description": "This is a fate",
"follows_id": 1,
}
**Example response:**
Expand All @@ -1176,16 +1176,17 @@ def post(self):
{
"status": "created",
"href": "/api/v1/fates/1",
"href": "/api/v1/fates/3",
"id": 3,
"creationEventTypeId": 1,
"completionEventTypeId": 2,
"intermediate": false,
"follows": 1,
"description": "This is a fate"
}
:reqjson int creationEventTypeId: the ID of the EventType that triggers this Fate
:regjson int completionEventTypeId: the ID of the EventType that closes Labors created by this Fate
:regjson boolean intermediate: If true, this Fate only creates Labors if simultaneously closing an existing Labor
:regjson int follows: (*optional*) The ID of the Fate this Fate must come after, or null
:regjson string description: (*optional*) The human readable description this Fate
:reqheader Content-Type: The server expects a json body specified with
Expand All @@ -1202,7 +1203,7 @@ def post(self):
try:
creation_event_type_id = self.jbody["creationEventTypeId"]
completion_event_type_id = self.jbody["completionEventTypeId"]
intermediate = self.jbody["intermediate"]
follows_id = self.jbody.get("follows_id")
description = self.jbody["description"]
except KeyError as err:
raise exc.BadRequest(
Expand Down Expand Up @@ -1230,7 +1231,7 @@ def post(self):
try:
fate = Fate.create(
self.session, creation_event_type, completion_event_type,
intermediate=intermediate, description=description
follows_id=follows_id, description=description
)
except IntegrityError as err:
raise exc.Conflict(err.orig.message)
Expand Down Expand Up @@ -1271,7 +1272,8 @@ def get(self):
"href": "/api/v1/fates/1",
"creationEventTypeId": 1,
"completionEventType": 2,
"intermediate": true|false,
"follows_id": null,
"precedes_ids": [3, 5],
"description": "This is a fate",
},
...
Expand Down Expand Up @@ -1374,13 +1376,13 @@ def put(self, id):
.. sourcecode:: http
PUT /api/v1/fates/1 HTTP/1.1
PUT /api/v1/fates/3 HTTP/1.1
Host: localhost
Content-Type: application/json
{
"description": "New desc",
"intermediate: true
"follows_id": 1
}
**Example response:**
Expand All @@ -1392,11 +1394,11 @@ def put(self, id):
{
"status": "ok",
"id": 1,
"href": "/api/v1/fates/1",
"id": 3,
"href": "/api/v1/fates/3",
"creationEventTypeId": 1,
"completionEventType": 2,
"intermediate": true,
"follows_id": 1,
"description": "New desc"
}
Expand All @@ -1420,27 +1422,11 @@ def put(self, id):
if not fate:
raise exc.NotFound("No such Fate {} found".format(id))

new_desc = None
new_intermediate = None
try:
if "description" in self.jbody:
new_desc = self.jbody["description"]
if "intermediate" in self.jbody:
new_intermediate = self.jbody['intermediate']
except KeyError as err:
raise exc.BadRequest(
"Missing Required Argument: {}".format(err.message)
)

try:
if new_desc:
fate = fate.update(
description=new_desc
)
if new_intermediate is not None:
fate = fate.update(
intermediate=new_intermediate
)
fate = fate.update(description=self.jbody["description"])
if "follows_id" in self.jbody:
fate = fate.update(follows_id=self.jbody['follows_id'])

except IntegrityError as err:
raise exc.Conflict(str(err.orig))
Expand Down
95 changes: 60 additions & 35 deletions hermes/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,34 +479,45 @@ class Fate(Model):
creation_type: the EventType that creates an Labor based on this Fate
completion_type: the EventType that closes an Labor created by this
Fate
intermediate: if True, this Fate only creates a Labor if also
closing a previous Labor
follows: indicates that this Fate only comes into affect if fulfilling
the Fate with this specified id
precedes: indicates which Fates are chained to come after this Fate
description: the optional human readable description of this Fate
_all_fates: cached list of all Fates
_intermediate_fates = cached list of all intermediate Fates
_intermediate_fates = cached list of all intermediate Fates (Fates that follow other Fates)
_starting_fates = cached list of all non-intermediate Fates
"""

__tablename__ = "fates"

id = Column(Integer, primary_key=True)

creation_type_id = Column(
Integer, ForeignKey("event_types.id"), nullable=False, index=True
)

creation_event_type = relationship(
EventType, lazy="joined", backref="auto_creates",
foreign_keys=[creation_type_id]
)
)

completion_type_id = Column(
Integer, ForeignKey("event_types.id"), nullable=False, index=True
)

completion_event_type = relationship(
EventType, lazy="joined", backref="auto_completes",
foreign_keys=[completion_type_id]
)
intermediate = Column(
Boolean, nullable=False, default=False

follows_id = Column(
Integer, ForeignKey("fates.id"), nullable=True, index=True
)

follows = relationship(
"Fate", lazy="joined", backref="precedes", remote_side=[id]
)

description = Column(String(2048), nullable=True)
__table_args__ = (
UniqueConstraint(
Expand All @@ -523,7 +534,7 @@ class Fate(Model):
@classmethod
def create(
cls, session,
creation_event_type, completion_event_type, intermediate=False,
creation_event_type, completion_event_type, follows_id=None,
description=None
):
"""Create a Fate
Expand All @@ -533,7 +544,7 @@ def create(
an labor creation
completion_event_type: an EventType that will trigger
an labor completion
intermediate: if True, this is a mid-workflow Fate
follows_id: id of the Fate this new Fate will follow; None if non-intermediate
description: optional description for display
Returns:
Expand All @@ -548,7 +559,7 @@ def create(
obj = cls(
creation_event_type=creation_event_type,
completion_event_type=completion_event_type,
intermediate=intermediate,
follows_id=follows_id,
description=description
)
obj.add(session)
Expand Down Expand Up @@ -580,10 +591,13 @@ def _refresh_cache(cls, session):
"id": fate.id,
"completion_type_id": fate.completion_type_id,
"creation_type_id": fate.creation_type_id,
"intermediate": fate.intermediate
"follows_id": fate.follows_id,
"precedes_ids": [
linked_fate.id for linked_fate in fate.precedes
]
}
Fate._all_fates.append(fate_dict)
if fate.intermediate:
if fate.follows_id:
Fate._intermediate_fates.append(fate_dict)
else:
Fate._starting_fates.append(fate_dict)
Expand Down Expand Up @@ -659,9 +673,9 @@ def question_the_fates(cls, session, events, quest=None):
all_new_labors = []
all_achieved_labors = []

# Get all the fates, in various categories, for easy reference
all_fates = Fate.get_all_fates(session)
starting_fates = Fate.get_starting_fates(session)
intermediate_fates = Fate.get_intermediate_fates(session)

# Query the database for open labors for hosts of which we have an event
open_labors = (
Expand All @@ -672,13 +686,16 @@ def question_the_fates(cls, session, events, quest=None):
)
).all()
)
host_labors = {}

# Let's sort and file the open labors by the host id
labors_by_hostid = {}
for labor in open_labors:
if labor.host_id in host_labors:
host_labors[labor.host_id].append(labor)
if labor.host_id in labors_by_hostid:
labors_by_hostid[labor.host_id].append(labor)
else:
host_labors[labor.host_id] = [labor]
labors_by_hostid[labor.host_id] = [labor]

# Now let's process each of the events and see what we need to do
for event in events:
host = event.host
event_type = event.event_type
Expand All @@ -696,28 +713,35 @@ def question_the_fates(cls, session, events, quest=None):
all_new_labors.append(new_labor_dict)

# Now, let's look at all the Fates that this EventType fulfills
# and let's make a list of the matching creation EventTypes
labor_types_fulfilled = []
# and pull them into an array for quicker processing
relevant_fates = []
for fate in all_fates:
if (
fate["completion_type_id"] == event_type.id
):
labor_types_fulfilled.append(fate["creation_type_id"])

# And with that list of fulfilled EventTypes, we can look for
# open Labors created by that kind of EventType and collect if
# for batch achievement
if host.id in host_labors:
for labor in host_labors[host.id]:
if labor.creation_event.event_type.id in labor_types_fulfilled:
all_achieved_labors.append({
"labor": labor,
"event": event
})
# Since are closing a Labor, we are free to see if an
# intermediate Fate is applicable
for fate in intermediate_fates:
if fate["creation_type_id"] == event_type.id:
relevant_fates.append(fate)

# And with that list of relevant Fates, we can look for
# open Labors where the Labor's EventType matches the Fate's
# creation EventType. We will collect the matching Labors into
# and array for batch achievement
if host.id in labors_by_hostid:
for labor in labors_by_hostid[host.id]:
for fate in relevant_fates:
if (
labor.creation_event.event_type.id
== fate["creation_type_id"]
):
all_achieved_labors.append({
"labor": labor,
"event": event
})

# Since this Fate closes this Labor, let's see
# if this Fate also precedes other Fates. If so,
# we can make the assumption that a new Labor
# should be created
if fate["precedes_ids"]:
new_labor_dict = {
"host_id": host.id,
"starting_labor_id": (
Expand Down Expand Up @@ -763,9 +787,10 @@ def to_dict(self, base_uri=None):
"""
out = {
"id": self.id,
"intermediate": True if self.intermediate else False,
"creationEventTypeId": self.creation_type_id,
"completionEventTypeId": self.completion_type_id,
"follows_id": self.follows_id,
"precedes_ids": [labor.id for labor in self.precedes],
"description": self.description,
}

Expand Down
4 changes: 1 addition & 3 deletions tests/api_tests/data/set1/fates.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@
"fate1": {
"creationEventTypeId": 1,
"completionEventTypeId": 2,
"intermediate": false,
"description": "A system that needs a reboot can be cleared by rebooting the machine."
},
"fate2": {
"creationEventTypeId": 3,
"completionEventTypeId": 4,
"intermediate": false,
"description": "A system that needs maintenance made ready before maintenance can occur."
},
"fate3": {
"creationEventTypeId": 4,
"completionEventTypeId": 5,
"intermediate": true,
"follows_id": 2,
"description": "Maintenance must be performed on a system that is prepped."
}
}

0 comments on commit 683f8b7

Please sign in to comment.