Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions docs/architecture/at-a-glance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Workflow Orchestrator At A Glance

!!! info
This article is for potential adopters considering Workflow Orchestrator (WFO) for their organization.
It avoids software implementation details and favors high-level terminology over the finer semantics of WFO's database models.


Workflow Orchestrator is a software framework for modeling the lifecycle of subscriptions to arbitrary products.
The framework is open-source and written in the Python programming language.
In organizations using WFO, software teams define **products** using the framework, along with **workflows** by which end-users manage each product's lifecycle.

When a user subscribes a customer to a product, they run a **create** workflow, which produces a **subscription** to that product.
That subscription can then be orchestrated by the other workflows associated with its product.
For example, **validate** workflows ensure WFO's database is kept in sync with any external resources allocated to a subscription, and **terminate** workflows deprovision the subscription along with those external resources.

## Scenario: Adding a Link
!!! info
In this section, we highlight the experience of an end-user: in this case, a network operator setting up a link between two routers.
While this example focuses on networking, WFO can be used for any kind of service, and provides no specific support for network products.

All screenshots are from the [example-orchestrator][example-orchestrator] project, which you can set up yourself to explore the product further.

Suppose that we have nodes in Amsterdam and London, and we want to establish a core link between them.

On the Subscriptions page, we can see that we already have a subscription for each node, so we create a link via the New Subscription dropdown.
![Create Core Link dropdown](../img/at-a-glance/create_core_link_dropdown.png)

We're then taken to a form for creating the new core link.
As seen below, the form is multi-part with choices pregenerated for the fields.

In this case, the choices are generated dynamically from a Netbox inventory. Workflow Orchestrator makes it easy for software developers to generate templated forms entirely on the backend using the [Pydantic Forms][pydantic-forms] package, with no front-end code required.

![Create Core Link form A](../img/at-a-glance/create_core_link_form_a.png)
![Create Core Link form B](../img/at-a-glance/create_core_link_form_b.png)

After submitting each part of the form, the Create workflow is started.
A workflow is composed of multiple steps, and the status of each step is visible in the UI as it completes.
Any step that generates data can be expanded in the UI to view its output.
Below, we can see the output of the Assign IPv6 Prefix step.

![Create Core Link workflow output](../img/at-a-glance/create_core_link_output.png)

A link in the upper-right corner of the workflow output (above) takes us to the subscription we created (below).

On the left, we can see various details about both the Core Link subscription, the ports on either end, and the nodes (also subscriptions) each port resides on.

On the right, the Actions dropdown lists the workflows available to us for further managing the subscription's lifecycle.

![Core Link subscription page](../img/at-a-glance/core_link_sub.png)

The General tab provides more extensive information about the subscription.

![Core Link subscription page general tab](../img/at-a-glance/core_link_sub_general.png)

## Additional Actions
Above, we saw the Actions available to manage a Core Link subscription after it was created.

`Modify core_link`: Users can define modify workflows to update the subscription database and any orchestrated external resources. These workflows facilitate changes to a subscription mid-lifecycle.

`Validate core_link`: Users can define validation workflows to verify the data in the WFO database against the external systems it manages. An error in a validation workflow places the corresponding subscription in an Out of Sync state, which flags it for remediation and blocks execution of its workflows. For example, if WFO manages a resource in Netbox and that resource is then edited directly in Netbox, this could be detected with a validation workflow.

`Terminate core_link`: Users can define terminate workflows in order to deprovision a subscription and any resources orchestrated on its behalf. Terminated subscriptions are still accessible for reference.

Users aren't limited to these workflows.
For example, it can be helpful to create distinct modify workflows for the same product to make unrelated changes.
Comment thread
eenblam marked this conversation as resolved.
Users can also create Tasks, which are simply workflows that aren't tied to any subscription.
Tasks can be run automatically via WFO's [scheduling](../../orchestrator-core/guides/tasks/) features, which can be customized to run at any time or frequency.

## Behind the Scenes

So what's actually happening behind the scenes when we run these workflows?

WFO provides none of the facilities for talking to Netbox, NSO, Ansible, etc.
These features are implemented by other packages or by the software team leveraging the framework.

In particular, each workflow is created from a number of user-defined **steps**.
A step is just a Python function that can be re-used by developers across workflows.
Usually, each step is responsible for interacting with an external resource: an inventory system, a database, a network device, an HTTP API, etc.
When a workflow fails for some reason, it can be retried beginning at whichever step failed.

If you want to see the code for yourself, the above example came from the [example-orchestrator][example-orchestrator] repo.
The Core Link producted is defined by a [Product][example-core-link-product-type] and its constituent [Product Blocks][example-core-link-product-blocks], along with its [workflows][example-core-link-workflows].
The workflows update the inventory system, Netbox, via [services/netbox.py][example-core-netbox-service].
By convention, user-defined modules for interacting with external services are organized under `services/`.

Comment thread
eenblam marked this conversation as resolved.
## Next Steps
To deploy your own Workflow Orchestrator, you will need two services:

* A web backend. You will develop a Python application that imports the [`orchestrator-core`][orchestrator-core] package as a dependency.
* A web frontend served over HTTP. You may develop a React application using the [`orchestrator-ui-components`][orchestrator-ui-library] NPM package, but it's easiest to start by modifying a copy of the [`example-orchestrator-ui`][example-orchestrator-ui] application for your own use.

Next, see [The Framework](./framework.md) for an architectural walkthrough of the software stack.

[nren-wikipedia]: https://en.wikipedia.org/wiki/National_research_and_education_network
[pydantic-forms]: https://workfloworchestrator.org/pydantic-forms/
[orchestrator-core]: https://github.com/workfloworchestrator/orchestrator-core
[orchestrator-ui-library]: https://github.com/workfloworchestrator/orchestrator-ui-library
[example-orchestrator]: https://github.com/workfloworchestrator/example-orchestrator
[example-orchestrator-ui]: https://github.com/workfloworchestrator/example-orchestrator-ui
[example-core-link-product-type]: https://github.com/workfloworchestrator/example-orchestrator/blob/main/products/product_types/core_link.py
[example-core-link-product-blocks]: https://github.com/workfloworchestrator/example-orchestrator/blob/main/products/product_blocks/core_link.py
[example-core-link-workflows]: https://github.com/workfloworchestrator/example-orchestrator/tree/main/workflows/core_link
[example-core-netbox-service]: https://github.com/workfloworchestrator/example-orchestrator/blob/main/services/netbox.py
42 changes: 42 additions & 0 deletions docs/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,48 @@ img[alt='pypi-downloads'], img[alt='npm-downloads'] {
filter: drop-shadow(1px 1px 5px white);
}

/*
* Make it discoverable that images open in a lightbox (mkdocs-glightbox): hovering one shows a
* zoom-in cursor and a magnifier badge in its top right corner. The badge is drawn on the link
* glightbox wraps around the image, so it disappears together with the plugin if it is ever
* removed, and never covers an image that is not clickable.
*/
:root {
--wfo-magnify-icon: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5m-.5 2h1v2h2v1h-2v2H9v-2H7V9h2z"/></svg>');
}

.md-typeset a.glightbox {
cursor: zoom-in;
display: inline-block;
line-height: 0;
position: relative;
}

.md-typeset a.glightbox::after {
background: rgba(0, 0, 0, .55) var(--wfo-magnify-icon) center / 1.1rem no-repeat;
border-radius: .2rem;
content: "";
height: 1.7rem;
opacity: 0;
pointer-events: none;
position: absolute;
right: .5rem;
top: .5rem;
transition: opacity 125ms;
width: 1.7rem;
}

.md-typeset a.glightbox:hover::after,
.md-typeset a.glightbox:focus-visible::after {
opacity: 1;
}

/* Dim the page behind the lightbox rather than blacking it out (glightbox defaults to .92 alpha). */
.goverlay {
background: rgba(0, 0, 0, .6);
backdrop-filter: blur(2px);
}

/*
* Label search results with the project they belong to.
*
Expand Down
Binary file added docs/img/at-a-glance/core_link_sub.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/at-a-glance/core_link_sub_general.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/at-a-glance/create_core_link_form_a.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/at-a-glance/create_core_link_form_b.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/at-a-glance/create_core_link_output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions docs/js/glightbox-instant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Re-apply the lightbox image links after an instant navigation.
*
* With the privacy plugin enabled, mkdocs-glightbox emits the image anchors
* without an href and fills it in from the img afterwards, using a one-shot
* inline script. navigation.instant (inherited from orchestrator-core) swaps
* in a new page without re-running that script, leaving the anchors empty:
* the lightbox then opens on nothing and shows a spinner forever.
*
* Loaded via extra_javascript, so this subscriber is registered before the
* lightbox.reload() one that mkdocs-glightbox appends to the body, and thus
* runs first on every page load.
*/
document$.subscribe(function () {
document.querySelectorAll("a.glightbox:not([href])").forEach(function (el) {
const img = el.querySelector("img")
if (img && img.src) {
el.setAttribute("href", img.src)
}
})
})
21 changes: 18 additions & 3 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ nav:
- Home: index.md
- Learn:
- Concepts:
- At A Glance: architecture/at-a-glance.md
# TODO (https://github.com/workfloworchestrator/workfloworchestrator.github.io/issues/63):
# The Orchestrator UI and Input Forms pages contain lots of project specific documentation and
# implementation detail.
Expand Down Expand Up @@ -90,6 +91,7 @@ extra_css:
extra_javascript:
- js/nav-persistence.js
- js/repo-source.js
- js/glightbox-instant.js
repo_name: workfloworchestrator
repo_url: https://github.com/workfloworchestrator
copyright: Copyright &copy; 2018 - 2026 Workflow Orchestrator Programme
Expand Down Expand Up @@ -122,10 +124,14 @@ plugins:
python:
inventories:
- https://docs.python.org/3/objects.inv
# Read every sub-project from its checkout rather than from the copy installed in the
# venv, so that docstrings cannot go stale: the checkouts track their main branch, while
# the installed versions only move when pyproject.toml is bumped. A page documenting a
# symbol added since the last bump fails the build ("Could not collect ...") otherwise.
# Each entry is the directory *containing* the package, not the package itself.
paths:
- lso/lso
# Read pydantic-forms from the checkout rather than the installed copy, so that
# attribute docstrings cannot go stale between edits and a re-install.
- orchestrator-core
- lso
- pydantic-forms
options:
show_source: true
Expand Down Expand Up @@ -185,5 +191,14 @@ plugins:
# orchestrator-ui-library plugins
- termynal
- awesome-pages
# Click a screenshot to open it full size in a lightbox. Listed last so it runs after the plugins
# that inject HTML, and thus also covers images pulled in from the sub-projects. Images already
# wrapped in a link (badges, for instance) are left alone; add the "off-glb" class to opt an
# individual image out.
- glightbox:
draggable: false
zoomable: true
skip_classes:
- off-glb
# workfloworchestrator.org general plugin that has to be loaded after awesome-pages
- macros
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ requires-python = ">=3.13,<3.15"
dependencies = [
"mkdocs>=1.6.1",
"mkdocs-drawio>=1.12.0",
"mkdocs-glightbox>=0.5.2",
"mkdocs-material[imaging]>=9.7.0",
"mkdocs-monorepo-plugin",
# orchestrator-core docs plugins
Expand Down
Loading
Loading