Skip to content

Commit

Permalink
Merge pull request #32 from diggyk/master
Browse files Browse the repository at this point in the history
Fixed command line to support new Fate schema.  Added more unit tests.
  • Loading branch information
gmjosack committed Aug 28, 2015
2 parents 683f8b7 + 061576d commit f67a6cb
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 38 deletions.
30 changes: 17 additions & 13 deletions bin/hermes
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,18 @@ def list_fates(args):

print "FATES: \n[id] created by => completed by\n"
for fate in fates:
print "[{}]{} {} => {}".format(
if not fate.get("follows_id"):
created_by_str = (
fate["creationEventType"]["category"] + " "
+ fate["creationEventType"]["state"]
)
else:
created_by_str = (
"(follows Fate {})".format(fate["follows_id"])
)
print "[{}] {} => {}".format(
fate["id"],
"*" if fate["intermediate"] else " ",
(
fate["creationEventType"]["category"]
+ " " + fate["creationEventType"]["state"]
),
created_by_str,
(
fate["completionEventType"]["category"]
+ " " + fate["completionEventType"]["state"]
Expand All @@ -266,7 +271,6 @@ def list_fates(args):
initial_indent="\t", subsequent_indent="\t"
)
print ""
print "* = intermediate. Intermediate Fates will not create new Labors."


def create_fate(args):
Expand All @@ -289,18 +293,18 @@ def create_fate(args):

creation_event_type = None
completion_event_type = None
for event_type in event_types:

for event_type in event_types:
if args.cc == event_type["category"] \
and args.cs == event_type["state"]:
creation_event_type = event_type
elif args.ct == event_type["id"]:
elif args.ct == str(event_type["id"]):
creation_event_type = event_type

if args.xc == event_type["category"] \
and args.xs == event_type["state"]:
completion_event_type = event_type
elif args.xt == event_type["id"]:
elif args.xt == str(event_type["id"]):
completion_event_type = event_type

if not creation_event_type:
Expand All @@ -314,7 +318,7 @@ def create_fate(args):
json = {
"creationEventTypeId": creation_event_type["id"],
"completionEventTypeId": completion_event_type["id"],
"intermediate": args.intermediate,
"follows_id": args.follows_id,
"description": args.description
}

Expand Down Expand Up @@ -930,8 +934,8 @@ def parse_cli_args():
dest="xt", type=str
)
fate_create_subparser.add_argument(
"-i", "--intermediate", action="store_true",
help="When flagged, this fate only creates a new labor if closing an existing one"
"--follows", type=int, default=None, dest="follows_id",
help="This Fate follows the Fate identified by ID here."
)
fate_create_subparser.add_argument(
"--description", help="The human readable description of this fate",
Expand Down
10 changes: 5 additions & 5 deletions db/bootstrap.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ UNLOCK TABLES;

LOCK TABLES `fates` WRITE;

INSERT INTO `fates` (`id`, `creation_type_id`, `completion_type_id`, `follows`, `description`)
INSERT INTO `fates` (`id`, `creation_type_id`, `completion_type_id`, `follows_id`, `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, 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.');
(1,1,2,NULL, 'A system that needs a reboot can be cleared by rebooting the machine.'),
(2,3,4,NULL, 'System must be released using "cloudbox release" so maintenance can be carried out.'),
(3,4,5,2, 'Maintenance must be performed on a system that is prepped.'),
(4,1,4,NULL, 'A system that needs a reboot can be released instead.');

UNLOCK TABLES;
4 changes: 2 additions & 2 deletions hermes/handlers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1288,10 +1288,10 @@ def get(self):
:statuscode 200: The request was successful.
:statuscode 401: The request was made without being logged in.
"""
fates = self.session.query(Fate)
fates = self.session.query(Fate).order_by(Fate.id)

offset, limit, expand = self.get_pagination_values()
hosts, total = self.paginate_query(fates, offset, limit)
fates, total = self.paginate_query(fates, offset, limit)

fates_json = []
for fate in fates.all():
Expand Down
19 changes: 17 additions & 2 deletions hermes/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,10 +521,12 @@ class Fate(Model):
description = Column(String(2048), nullable=True)
__table_args__ = (
UniqueConstraint(
creation_type_id, completion_type_id,
creation_type_id, completion_type_id, follows_id,
name='_creation_completion_uc'
),
Index("fate_idx", id, creation_type_id, completion_type_id),
Index(
"fate_idx", id, creation_type_id, completion_type_id, follows_id
),
)

_all_fates = None
Expand Down Expand Up @@ -555,6 +557,19 @@ def create(
"Creation EventType and completion EventType are required"
)

if follows_id:
preceding_fate = session.query(Fate).get(follows_id)
if not preceding_fate:
raise exc.ValidationError(
"Fate cannot follow a non-existent Fate {}".format(follows_id)
)

if preceding_fate.completion_event_type != creation_event_type:
raise exc.ValidationError(
"Creation EventType for this Fate and the completion "
"EventType for the preceding Fate do not match"
)

try:
obj = cls(
creation_event_type=creation_event_type,
Expand Down
2 changes: 1 addition & 1 deletion hermes/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.2.6"
__version__ = "0.2.7"
60 changes: 49 additions & 11 deletions tests/model_tests/test_fates.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@
from sqlalchemy.exc import IntegrityError

from hermes import exc
from hermes import models
from hermes.models import Fate, EventType

from .fixtures import db_engine, session, sample_data1


def test_creation(sample_data1):
event_types = sample_data1.query(models.EventType).all()
event_types = sample_data1.query(EventType).all()
assert len(event_types) == 7

event_type6 = event_types[5]
event_type7 = event_types[6]

models.Fate.create(
Fate.create(
sample_data1, event_type6, event_type7, description="New fate"
)
sample_data1.commit()

fates = sample_data1.query(models.Fate).all()
fates = sample_data1.query(Fate).all()

# the total number of fates should be 5 now. We care about the new one
assert len(fates) == 5
Expand All @@ -39,13 +39,51 @@ def test_creation(sample_data1):


def test_duplicate(sample_data1):
event_types = sample_data1.query(models.EventType).all()
assert len(event_types) == 7

event_type1 = event_types[0]
event_type2 = event_types[1]
event_type4 = sample_data1.query(EventType).get(4)
event_type5 = sample_data1.query(EventType).get(5)

with pytest.raises(IntegrityError):
models.Fate.create(
sample_data1, event_type1, event_type2, description="Dup fate"
Fate.create(
sample_data1, event_type4, event_type5, description="Dup fate",
follows_id=2
)


def test_uniqueness_by_follows_id(sample_data1):
"""We should be able to create a fate with the same event_types if the
follows_id is unique"""

event_type4 = sample_data1.query(EventType).get(4)
event_type5 = sample_data1.query(EventType).get(5)

fate = Fate.create(
sample_data1, event_type4, event_type5, description="Unique fate",
follows_id=None
)


def test_follows_id_constraint(sample_data1):
"""When specifying that a Fate follows another Fate, make sure the
completing EventType for the preceding Fate matches the creation EventType
forthe following Fate.
"""

event_type1 = sample_data1.query(EventType).get(1)
event_type5 = sample_data1.query(EventType).get(5)

with pytest.raises(exc.ValidationError):
Fate.create(
sample_data1, event_type1, event_type5, description="Wrong fate",
follows_id=2
)

def test_follows_id_valid(sample_data1):
event_type1 = sample_data1.query(EventType).get(1)
event_type5 = sample_data1.query(EventType).get(5)

# There is no Fate 20
with pytest.raises(exc.ValidationError):
Fate.create(
sample_data1, event_type1, event_type5, description="Coolio",
follows_id=20
)
58 changes: 54 additions & 4 deletions tests/model_tests/test_labors.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from hermes import exc
from hermes.models import Event, EventType, Fate, Host, Labor

from .fixtures import db_engine, session, sample_data1
from .fixtures import db_engine, session, sample_data1, sample_data2


def test_lifecycle1(sample_data1):
Expand Down Expand Up @@ -245,21 +245,71 @@ def test_cannot_start_in_midworkflow(sample_data1):
labors = sample_data1.query(Labor).all()
assert len(labors) == 0

fate = sample_data1.query(Fate).get(3)
event_type_d = sample_data1.query(EventType).get(4)
host = sample_data1.query(Host).get(1)

Event.create(sample_data1, host, "system", fate.creation_event_type)
Event.create(sample_data1, host, "system", event_type_d)

event = (
sample_data1.query(Event)
.order_by(desc(Event.id)).first()
)

assert event.host == host
assert event.event_type == fate.creation_event_type
assert event.event_type == event_type_d

labors = Labor.get_open_unacknowledged(sample_data1)
assert len(labors) == 0


def test_longer_chain(sample_data2):
"""Test chained labors A->B->C->D"""
labors = sample_data2.query(Labor).all()
assert len(labors) == 0

event_type_a = sample_data2.query(EventType).get(1)
event_type_b = sample_data2.query(EventType).get(2)
event_type_c = sample_data2.query(EventType).get(3)
event_type_d = sample_data2.query(EventType).get(4)

host = sample_data2.query(Host).get(1)

event_a = Event.create(sample_data2, host, "system", event_type_a)

# We will aggressively validate the events created only for event A
event = (
sample_data2.query(Event)
.order_by(desc(Event.id)).first()
)
assert event == event_a
assert event.host == host
assert event.event_type == event_type_a

labors = Labor.get_open_unacknowledged(sample_data2)
assert len(labors) == 1
assert len(host.labors) == 1
assert labors[0].starting_labor_id is None
starting_labor_id = labors[0].id

event_b = Event.create(sample_data2, host, "system", event_type_b)
labors = Labor.get_open_unacknowledged(sample_data2)
assert len(labors) == 1
assert len(host.labors) == 2
assert labors[0].starting_labor_id == starting_labor_id

event_c = Event.create(sample_data2, host, "system", event_type_c)
labors = Labor.get_open_unacknowledged(sample_data2)
assert len(labors) == 1
assert len(host.labors) == 3
assert labors[0].starting_labor_id == starting_labor_id

# This last event closes the final labor but does not create a new labor
event_d = Event.create(sample_data2, host, "system", event_type_d)
labors = Labor.get_open_unacknowledged(sample_data2)
assert len(labors) == 0
assert len(host.labors) == 3





0 comments on commit f67a6cb

Please sign in to comment.