Skip to content

How To: Author a ForgeTree

Travis Jensen edited this page May 28, 2020 · 29 revisions

How-To: Author a ForgeTree

Suggested Readings

Using the ForgeEditor

Creating your first ForgeTree

Roslyn and C#|

Adding a TreeNode

How to Execute a ForgeAction with ActionInput

Native ForgeActions

Adding a Subroutine Tree

Using Session

Using UserContext

Using RetryPolicy and Timeout

How-To: Author a ForgeTree

This page provides a detailed guide how to author a ForgeTree and utilize the various features of Forge.

Suggested Readings

Overview of ForgeTree Properties

How Forge TreeWalker Walks the ForgeTree

Using the ForgeEditor

ForgeTree and Subroutines

Crafting your ForgeTree

Comprehensive ForgeTree Example

{
    "RootTreeNodeKey": "Root",
    "Tree": {
        "Root": {
            "Type": "Selection",
            "ChildSelector": [
                {
                    "Label": "Container",
                    "ShouldSelect": "C#|UserContext.ResourceType == \"Container\"",
                    "Child": "Container"
                },
                {
                    "Label": "Node",
                    "ShouldSelect": "C#|UserContext.ResourceType == \"Node\"",
                    "Child": "Node"
                }
            ]
        },
        "Container": {
            "Type": "Action",
            "Actions": {
                "CollectDiagnosticsAction_Container": {
                    "Action": "CollectDiagnosticsAction"
                }
            },
            "ChildSelector": [
                {
                    "Label": "Tardigrade",
                    "ShouldSelect": "C#|Session.GetLastActionResponse().Status == \"Success\"",
                    "Child": "Tardigrade"
                }
            ]
        },
        "Tardigrade": {
            "Type": "Action",
            "Actions": {
                "TardigradeAction_Tardigrade": {
                    "Action": "TardigradeAction",
                    "Input": {
                        "Context": "ContainerFault",
                        "EnableV2": true,
                        "DiagnosticData": "C#|Session.GetLastActionResponse().Output"
                    }
                }
            },
            "ChildSelector": [
                {
                    "Label": "Tardigrade_Success",
                    "ShouldSelect": "C#|(await Session.GetOutputAsync(\"Tardigrade_TardigradeAction\")).Status == \"Success\"",
                    "Child": "Tardigrade_Success"
                },
                {
                    "Label": "Tardigrade_Failure",
                    "Child": "Tardigrade_Failure"
                }
            ]
        },
        "Tardigrade_Failure": {
            "Type": "Leaf"
        },
        "Tardigrade_Success": {
            "Type": "Leaf",
                "Actions": {
                    "LeafNodeSummaryAction_Tardigrade_Success": {
                        "Action": "LeafNodeSummaryAction",
                        "Input": {
                            "Status": "C#|string.Format(\"{0}_{1}\", \"ContainerFaultScenario\", (await Session.GetLastActionResponseAsync()).Status)",
                            "StatusCode": 0,
                            "Output": {
                                "ActionOutput": "C#|(await Session.GetLastActionResponseAsync()).Output",
                                "DiagnosticsOutput": "C#|(await Session.GetOutputAsync(\"Container_CollectDiagnosticsAction\")).Output"
                            }
                        }
                    }
                }
        },
        "Node": {
            "Type": "Selection",
            "Properties": {
                "Notes": "Decision to Reboot or Evacuate is decided in UserContext.ShouldReboot()."
            },
            "ChildSelector": [
                {
                    "Label": "Reboot",
                    "ShouldSelect": "C#|UserContext.ShouldReboot()",
                    "Child": "Reboot"
                },
                {
                    "Label": "Evacuate",
                    "Child": "Evacuate"
                }
            ]
        },
        "Reboot": {
            "Type": "Action",
            "Actions": {
                "RebootAction_Reboot": {
                    "Action": "RebootAction",
                    "RetryPolicy": {
                        "Type": "FixedCount",
                        "MaxRetryCount": 3,
                        "MinBackoffMs": 1000
                    },
                    "ContinuationOnRetryExhaustion": true
                }
            }
        },
        "Evacuate": {
            "Type": "Action",
            "Actions": {
                "EvacuateAction_Evacuate": {
                    "Action": "EvacuateAction",
                    "Timeout": 60000,
                    "RetryPolicy": {
                        "Type": "ExponentialBackoff",
                        "MinBackoffMs": 1000,
                        "MaxBackoffMs": 30000
                    },
                    "ContinuationOnTimeout": true
                },
                "NotifyCustomerAction_Evacuate": {
                    "Action": "NotifyCustomerAction",
                    "Properties": "Notify customer in parallel with the impact."
                }
            },
            "Timeout": "C#|UserContext.GetTimeoutForEvacuatingAndNotifyingCustomer()"
        }
    }
}

ForgeTree Tree

{
    "RootTreeNodeKey": "Root",
    "Tree": {
        "Root": {

Tree is a dictionary that maps unique TreeNodeKey strings to TreeNodes. In the example we see several TreeNodeKeys, such as: Root, Container, Node, etc..

RootTreeNodeKey defines the suggested "Root" TreeNodeKey that should be visited first when walking the tree. Without this property, the callers of WalkTree would not know which TreeNodeKey to visit first since the TreeNodes are organized in a dictionary.

The default value of RootTreeNodeKey is "Root."

TreeNode - Selection TreeNodeType and ChildSelector

        "Root": {
            "Type": "Selection",
            "ChildSelector": [
                {
                    "Label": "Container",
                    "ShouldSelect": "C#|UserContext.ResourceType == \"Container\"",
                    "Child": "Container"
                },
                {
                    "Label": "Node",
                    "ShouldSelect": "C#|UserContext.ResourceType == \"Node\"",
                    "Child": "Node"
                }
            ]
        },
Selection TreeNodeType

Let's look at our first TreeNode, which is a Selection node. There are 4 TreeNodeTypes: Selection, Action, Leaf, and Subroutine. Selection type nodes have the following behavior:

  • ChildSelector property is defined. Attempts to select a child node to visit next.
  • Does not execute Actions.
  • Does not consume the TreeNode Timeout.
ChildSelector

ChildSelectors define the connections to child TreeNodes, as well as the conditions required to visit each child. Several TreeNodeTypes can use ChildSelectors, including: Selection, Action, and Subroutine. Let's break down each property:

  • Label - This is used only in ForgeEditor for visualization purposes. It is the text that hovers above each child TreeNode. Recommend using this to describe the ShouldSelect statement. This gives context to why the child is being visited.
  • ShouldSelect - A string code-snippet that can be parsed and evaluated to a boolean value. If the expression is true, visit the attached Child TreeNode. If the expression is empty, evaluate to true by default. We'll dive more into "C#|" and Roslyn further down. For now, you can read the first ShouldSelect statement as follows: If the ResourceType equals Container, then visit the Container TreeNode.
  • Child - The string TreeNodeKey that will be visited if the attached ShouldSelect expression evaluates to true.

TreeNode - Action TreeNodeType

        "Container": {
            "Type": "Action",
            "Actions": {
                "CollectDiagnosticsAction_Container": {
                    "Action": "CollectDiagnosticsAction"
                }
            },
            "ChildSelector": [
                {
                    "Label": "Tardigrade",
                    "ShouldSelect": "C#|Session.GetLastActionResponse().Status == \"Success\"",
                    "Child": "Tardigrade"
                }
            ]
        },

Our next TreeNode is an Action node. Action type nodes have the following behavior:

  • Executes Actions. Must contain at least one Action.
  • If multiple Actions are defined, executes them all in parallel.
  • Cannot execute a SubroutineAction.
  • Optionally, ChildSelector can be defined. Child selection happens after executing Actions, as long as there are no unhandled exceptions/timeouts.
  • Optionally, TreeNode-level Timeout can be defined. This is the timeout in milliseconds for executing all TreeActions. If the timeout is hit, a TimeoutException will be thrown and the tree walker session will be cancelled.
TreeAction Actions

Actions is a dictionary that maps unique TreeActionKey strings to TreeActions. In the example we see several TreeActionKeys, such as: CollectDiagnosticsAction_Container, TardigradeAction_Tardigrade, etc..

TreeActionKeys must be unique across each ForgeTree. This is required because Forge uses the TreeActionKey when persisting some state. Optionally, the application owner could decide to enforce globally unique TreeActionKeys across all ForgeTrees.

Creating your first ForgeTree

Roslyn and C#|

Adding a TreeNode

How to Execute a ForgeAction with ActionInput

Native ForgeActions

Adding a Subroutine Tree

Using Session

Using UserContext

Using RetryPolicy and Timeout

Clone this wiki locally