Skip to content

Instantly share code, notes, and snippets.

@mgreenly
Last active February 26, 2020 01:22
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save mgreenly/42475f8d3f4e5ce354e188629ff09d3a to your computer and use it in GitHub Desktop.
require 'pp'
Rules = [
{
is: { state: 'created', status: 'pending', provider: 'aws' },
in: 'taggable',
to: { state: 'tagged' },
as: { state: 'tagging' },
on: 'housekeeper'
},
{
is: { state: 'tagged', status: 'pending', provider: 'aws' },
in: 'executable',
to: { state: 'executed' },
as: { state: 'executing' },
on: 'agent'
},
{
is: { state: 'created', status: 'pending', provider: 'dc' },
in: 'excutable',
to: { state: 'executed' },
as: { state: 'executing' },
on: 'agent'
},
{
is: { state: 'executed', status: 'pending' },
in: 'excutable',
to: { state: 'notified' },
as: { state: 'notifying' },
on: 'housekeeper'
},
{
is: { state: 'notified', status: 'pending' },
in: 'completable',
to: { state: 'completed' },
as: { state: 'completing' },
on: 'housekeeper'
},
{
is: { state: 'created', status: 'canceling' },
in: 'excutable',
to: { state: 'completed' },
as: { state: 'completing' },
on: 'housekeeper'
},
{
is: { state: 'created', status: 'aborting' },
in: 'notifiable',
to: { state: 'notified' },
as: { state: 'notifying' },
on: 'housekeeper'
},
]
puts 'digraph G {'
clusters = []
Rules.each do |rule|
is_state = rule[:is][:state]
as_state = rule[:as][:state]
to_state = rule[:to][:state]
if as_state
conditions = rule[:is].values.map{|c| c.to_s + '?' }.join(' & ')
print " #{is_state} -> #{as_state} "
print ' [label="'
print conditions
puts '"]'
clusters << { from: as_state, to: to_state, on: rule[:on] }
else
puts " #{is_state} -> #{to_state} "
end
end
clusters.uniq.each_with_index do |cluster, index|
puts " subgraph cluster#{index} {"
print ' label="'
print cluster[:on]
puts '"'
puts " #{cluster[:from]} -> #{cluster[:to]} "
puts " }"
end
puts '}'
// dot -Tpng graph1.gv -o ~/Downloads/graph1.png
digraph G {
created [shape=box];
completed [shape=box];
created -> tagged [label="pending?"];
tagged -> executed [label="pending?"]
executed -> untagged [label="pending?"];
untagged -> notified [label="pending?"];
notified -> completed [label="pending?"];
created -> completed [label="canceling?"];
tagged -> untagged [label="canceling?"];
executed -> untagged [label="canceling?"];
untagged -> completed [label="canceling?"];
notified -> completed [label="canceling?"];
}
// dot -Tpng graph1.gv -o ~/Downloads/graph1.png
// dot -v -Tpdf graph1.gv -o ~/Downloads/graph1.pdf
// created pending aws =
// created pending dc =
// created cancelling _ =
// created aborting _ =
digraph G {
label = "workflow: 'Thread Dump'"
labelloc = top
labeljust = left
created [shape=box];
succeeded [shape=box];
aborted [shape=box];
canceled [shape=box];
edge [color="green"]
created -> tagging [label="'taggable'\ncreated? & pending? & aws?"];
created -> executing_dc [label="'executable'\ncreated? & pending? & dc?"];
subgraph cluster0 {
label = "housekeeper"
tagging -> tagged
}
tagged -> executing_aws [label="'executable'\ntagged? & pending? & aws?"]
subgraph cluster1 {
label = "agent dc"
executing_dc -> executed_dc
}
executed_dc -> notifying [label="'notifiable'\nexecuted? & pending? & dc?"];
subgraph cluster1a {
label = "agent aws"
executing_aws -> executed_aws
}
executed_aws -> untaging [label="'untaggable'\nexecuted? & pending? & aws?"];
subgraph cluster2 {
label = "housekeeper"
untaging -> untagged
}
untagged -> notifying [label="'notifiable'\nuntagged? & pending?"];
subgraph cluster3 {
label = "housekeeper"
notifying -> notified
}
notified -> completing [label="'completable'\nnotified? & pending?"];
subgraph cluster4 {
label = "housekeeper"
completing -> succeeded [label="pending?"];
edge [color="red"]
completing -> canceled [label="canceling?"];
completing -> aborted [label="aborting?"];
}
edge [color="red"]
created -> completing [label="'completable'\ncreated? & canceling?"];
tagged -> untaging [label="'untaggable'\ntagged? & canceling?"];
executed_aws -> untaging [label="'untaggable'\nexecuted? & canceling? & aws?"];
executed_dc -> completing [label="'completable'\nexecuted? & canceling? & dc?"];
untagged -> completing [label="'completable'\nuntagged? & canceling?"];
notified -> completing [label="'completable'\nnotified? & canceling?"];
created -> notifying [label="'notifiable'\ncreated? & aborting?"];
tagged -> untaging [label="'untaggable'\ntagged? & aborting?"];
executed_aws -> untaging [label="'untaggable'\nexecuted? & aborting? & aws?"];
executed_dc -> notifying [label="'notifiable'\nexecuted? & aborting? & dc?"];
untagged -> notifying [label="'notifiable'\nuntagged? & aborting?"];
notified -> completing [label="'completable'\nnotified? & aborting?"];
}
# A workflow is a directed acyclic graph (DAG).
#
# It must always move forward toward completed, it can't loop back on it's self.
#
Transitions = [
{ status: 'pending', state: 'created', action: 'tag' },
{ status: 'pending', state: 'tagged', action: 'execute' },
{ status: 'pending', state: 'executed', action: 'untag' },
{ status: 'pending', state: 'untagged', action: 'notify' },
{ status: 'pending', state: 'notified', action: 'complete' },
{ status: 'cancelling', state: 'created', action: 'complete' },
{ status: 'cancelling', state: 'tagged', action: 'untag' },
{ status: 'cancelling', state: 'executed', action: 'untag' },
{ status: 'cancelling', state: 'untagged', action: 'complete' },
{ status: 'cancelling', state: 'notified', action: 'complete' },
{ status: 'aborting', state: 'created', action: 'complete' },
{ status: 'aborting', state: 'tagged', action: 'untag' },
{ status: 'aborting', state: 'executed', action: 'untag' },
{ status: 'aborting', state: 'untagged', action: 'complete' },
{ status: 'aborting', state: 'notified', action: 'complete' },
]
# make sure every possible transiton is handled exactly once
errors = []
Transitions.map{|t| t[:status] }.uniq.each do |status|
Transitions.map{|t| t[:state] }.uniq.each do |state|
result = Transitions.select { |t| t[:status] == status && t[:state] == state }
errors << "Missing handler for transition from: '#{status}-#{state}'" if result.empty?
errors << "Duplicate handler for transition from: '#{status}-#{state}'" if result.count > 1
end
end
errors.each do |error|
puts error
end
require 'pp'
class Array
def intersperse(separator)
(inject([]) { |a,v| a+[v,separator] })[0...-1]
end
end
RULES = [
{
is: { state: 'created', status: 'pending', provider: 'aws' },
in: 'taggable',
to: { state: 'tagged' },
as: { state: 'tagging' },
on: 'housekeeper'
},
{
is: { state: 'tagged', status: 'pending', provider: 'aws' },
in: 'executable_aws',
to: { state: 'executed_aws' },
as: { state: 'executing_aws' },
on: 'agent-aws'
},
{
is: { state: 'created', status: 'pending', provider: 'dc' },
in: 'excutable_dc',
to: { state: 'executed_dc' },
as: { state: 'executing_dc' },
on: 'agent-dc'
},
{
is: { state: 'executed_dc', status: 'pending' },
in: 'notifiable',
to: { state: 'notified' },
as: { state: 'notifying' },
on: 'housekeeper'
},
{
is: { state: 'executed_aws', status: 'pending' },
in: 'untaggable',
to: { state: 'untagged' },
as: { state: 'untagging' },
on: 'housekeeper'
},
{
is: { state: 'untagged', status: 'pending' },
in: 'untaggable',
to: { state: 'notified' },
as: { state: 'notifying' },
on: 'housekeeper'
},
{
is: { state: 'notified', status: 'pending' },
in: 'completable',
to: { state: 'completed' },
as: { state: 'completing' },
on: 'housekeeper'
},
{
is: { state: 'created', status: 'canceling' },
in: 'completable',
to: { state: 'completed' },
as: { state: 'completing' },
on: 'housekeeper'
},
{
is: { state: 'tagged', status: 'canceling' },
in: 'untaggable',
to: { state: 'untagged' },
as: { state: 'untagging' },
on: 'housekeeper'
},
{
is: { state: 'executed_aws', status: 'canceling' },
in: 'untaggable',
to: { state: 'untagged' },
as: { state: 'untagging' },
on: 'housekeeper'
},
{
is: { state: 'executed_dc', status: 'canceling' },
in: 'completable',
to: { state: 'completed' },
as: { state: 'completing' },
on: 'housekeeper'
},
{
is: { state: 'notified', status: 'canceling' },
in: 'completable',
to: { state: 'completed' },
as: { state: 'completing' },
on: 'housekeeper'
},
{
is: { state: 'untagged', status: 'canceling' },
in: 'completable',
to: { state: 'completed' },
as: { state: 'completing' },
on: 'housekeeper'
},
{
is: { state: 'created', status: 'aborting' },
in: 'completable',
to: { state: 'completed' },
as: { state: 'completing' },
on: 'housekeeper'
},
{
is: { state: 'tagged', status: 'aborting' },
in: 'untagging',
to: { state: 'untagged' },
as: { state: 'untagging' },
on: 'housekeeper'
},
]
def clusters
RULES.map do |rule|
{ from: rule[:as][:state], to: rule[:to][:state], on: rule[:on] }
end.uniq.each_with_index.map do |cluster, index|
[
" subgraph cluster#{index} {",
" label=\"#{cluster[:on]}\"",
" #{cluster[:from]} -> #{cluster[:to]}",
" }"
]
end.intersperse('')
end
def conditions(rule)
["workflow = ThreadDump"].concat(rule[:is].map{ |k, v| "#{k} = #{v}" }).join('\\n & ')
end
def group(rule)
"'#{rule[:in]}'\\n"
end
def label(rule)
"[label=\"#{conditions(rule)}\"]"
end
def transition(rule)
a = rule[:is][:state]
b = rule[:as][:state]
"#{a} -> #{b}"
end
def rules(val)
RULES.select do |rule|
rule[:is][:status] == val
end.map do |rule|
" #{transition(rule)} #{label(rule)}"
end
end
def terminals
[
'created',
'completed'
].map{|name| "#{name} [shape=box]" }
end
def dot
[
'digraph G {',
terminals,
'edge [color="green"]',
clusters,
rules('pending'),
'edge [color="purple"]',
rules('canceling'),
'edge [color="red"]',
rules('aborting'),
'}'
].intersperse('').flatten.join("\n")
end
puts dot
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment