On fuzzing a charm's lifecycle

Sometimes you’d like your integration tests to simulate lots of different event sequences that might be thrown at your charm. What if immediately after install you get a pebble-ready? What if you get it before install? What if you get it again, twice in a row, after a relation-joined event in the middle of a charm’s operation phase.

This is why I wrote the CharmEventSimulator, a python class that takes care of generating valid but random sequences of events based on what you decide are the plausible actions and events that might hit a charm during its lifetime. You can find it here.

It’s just a prototype and could use some validation and testing, but the idea is this: suppose you have a Charm with a storage mount called storage1, a peer relation called replicas, and a non-peer relation called http. You write:

sim = CharmEventSimulator(
# relations & storage we start with
    storage_mounts=[  
        StorageMount('storage1')],
    relations=[
        Relation('http'),
        Relation('replicas', is_peer=True)
    ],
# relations & storage that ATM we don't have but might have in 
# the future if another charm is deployed and related to this one
    potential_relations=[  
        Relation('mongo')
    ],
    potential_storage_mounts=[
        StorageMount('ephemeral1')
    ]
)
sim.run()
sim.pprint()

and you obtain:

simulation:
PHASE Phase.setup:
    Event :: workload-pebble-ready
    Event :: storage1-storage-attached
    Event :: install
    Event :: update-status
    Event :: replicas-relation-created
    Event :: leader-elected
    Event :: config-changed
    Event :: start

PHASE Phase.operation:
    Action :: Source.user --> 'config-change'
    Event :: config-changed
    Action :: Source.user --> 'scale+'
    Event :: replicas-relation-joined
    Event :: replicas-relation-changed
    Action :: Source.user --> 'attach'(ephemeral1)
    Event :: update-status
    Event :: ephemeral1-storage-attached
    Action :: Source.other_charm --> 'change'(replicas)
    Event :: replicas-relation-changed
    Action :: Source.other_charm --> 'change'(replicas)
    Event :: replicas-relation-changed
    Action :: Source.user --> 'create'(mongo)
    Event :: workload-pebble-ready
    Event :: mongo-relation-created
    Action :: Source.user --> 'break'(http)
    Event :: http-relation-broken
    Action :: Source.this_charm --> 'change'(http)
    Event :: http-relation-changed
    Action :: Source.user --> 'leadership_change'
    Event :: leader-settings-changed
    Action :: Source.other_charm --> 'change'(replicas)
    Event :: replicas-relation-changed
    Action :: Source.user --> 'detach'(storage1)
    Event :: workload-pebble-ready
    Event :: storage1-storage-detached

PHASE Phase.teardown:
    Event :: replicas-relation-broken
    Event :: mongo-relation-broken
    Event :: update-status
    Event :: ephemeral1-storage-detached
    Event :: stop
    Event :: remove

end.

This is a temporally-ordered sequence of events that realistically emulates a possible happy-path execution of your charm in a unit. It tells a story:

  • This is a charm that begins with 1 unit;
  • the workload container is immediately ready, after which a standard startup phase follows;
  • The user changes the charm’s config;
  • The user scales the unit up; this triggers replicas-relation-joined->replicas-relation-changed.
  • The user attaches ephemeral1 storage; triggering a 'ephemeral1-storage-attached event

And so on and so forth.

To be continued!

4 Likes