Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upCustomStateMachines Documentation
CustomStateMachines
Within CloudForms, as a service is provisioned or retired, automate entry points may be freely chosen from service catalog item design. However, as it comes to provisioning or retiring VM(s) related to this service, it goes by default to the same entry points. By default, All VMs share the same provisioning/retirement entry points whatever is their specific context or workflow.
This brick will cover a way to dynamically map VMs State Machines based on context criterias, both for provisioning and retirement, both for Infrastructure and Cloud VMs.
CustomStateMachines have been currently tested on the following release :
- CloudForms 4.2
- CloudForms 4.5
- CloudForms 4.6
Initial tested features were :
- Infrastructure VM provisioning and retirement on RHV and VMware environments.
- Cloud VM provisioning and retirement on OpenStack (OSP 10) and AWS EC2.
Use cases / detailed presentation
There may be a lot of them : from rationalizing dev/qa/pre-prod/prod environments up to implementing dedicated Ansible Embedded workflows in VM post-provisioning or retirement State Machines (CF 4.5+).
Here are some of them :
- Several people working at VM state machine level, needing to have dedicated states without disturbing others.
- As services may be provisioned with pre-requisites (i.e. IPAM, CMDB,...) ability to still use VM Lifecycle raw provisioning, or other ways if required.
- Specifying dedicated VM provisioning workflows based on typology while reducing complexity of huge StateMachines with lot of messages maintenance (i.e VM level Ansible Embedded workflows)
- Quick partial rollbacks without impacting all provisionings done on the platform (State Machine switching by tagging).
By default, domains may be overloaded. So a class/instance may mask and replace any others of the same name within domains hierarchy.
In example, an A and a B domains may exist, and may be switched/enabled/disabled in the hierarchy so that the target state machine may be elected from the highest priority active domain. However, only one of them may be called at a time, and will be called independently of the VM context or it's possibly specific workflows requirements. If A domain is active at highest level, then, there won't be default ways calling the B state machines that A overload at that time. In case we need to have different ways provisioning VMs at the same time we have no options.
If different VM typologies exist (i.e. different infrastructure contexts or workflows) the only eligible state machine at a time will have to implement all logics to check what have to be done or not. In time, that may lead to complex and hard to maintain solution.
This brick proposes a way to address the point by selecting VM StateMachines depending on dynamic criterias.
As a VM is provisioned/retired, a contextual item will be checked that will automatically re-vectorize the intended action to a dedicated State Machine. Context criterias may be whatever you are aware of within Automation, matching your project requirements. It could be a tenant, a template component value, a tag, or whatever else you can grab and base your logic on.
In this implementation, we will consider tags setted on VMs catalog items pointing at names for classes and/or instances to the target state machines.
So, back to our use case, if we have specific requirements or typologies for domain A and B VMs, we will simply require to tag the catalog items of those VMs accordingly so they trigger appropriate StateMachines. We won't have to care or interfere with regular domains hierarchy. As far as State Machines entry point names (of our choice) are unique they will be called for targeted VMs, within their specific context.
Catalog items that are not specifically tagged will still be managed following default policy, so this will be totally transparent to them. Finally, this implementation covers both Infrastructure and Cloud VM provisioning and retirement.
Quick Start Guide
A word about naming conventions
Discussions arose within the community about naming conventions for classes, instances and methods. Initial convention was that classes and instances were named in CamelCase (i.e. MyClass, MyInstance) whereas methods were named in camel_case (ie my_action). As well Class and Methods should have the same name if possible, that should be (theoretically), if a method is not used by several instances. New convention says that classes should remain CamelCase whereas instances should now be snake_case, such as methods are.
This implementation has been initiated some time ago and implement the original convention where classes and instances are both named in CamelCase. As we define state machines names from tags (that do not support upper case characters) we currently translate them from snake to camel case for both. So, if targeting a class/instance named as MyClass and MyInstance, tags values will need to be my_class and my_instance. Evolving to the new convention is not an issue from the technical side (one code line and one condition), but from the functional side it is somewhat intrusive (existing instances renaming) and would mean that behaviour between class and instance name build would not be homogeneous.
Feel free to share your mind if you have some constructive thoughts about this point.
Targeted Integration
We will assume that we want to set-up a dedicated set of Infrastructure State Machines for a specific VM Catalog Item provisioning and retirement. The exact same principles would apply to Cloud VM provisioning (replacing Infrastructure paths with Cloud paths). As well, this brick is transparent to the provisioning kind (template or clone). In our example we will provision from template.
The infrastructure provisioning/retirement namespace have not to be changed.
They will remain /Infrastucture/VM/Provisioning/StateMachines/ for provisioning, and /Infrastructure/VM/Retirement/StateMachines/ for retirement.
In the following, those respective namespaces are considered default prefixes on all paths where it applies.
So, in those respective namespaces, default State Machines for our example are :
-
VMProvision_VM/templatefor provisioning (instance depends onmiq_provision.provision_type). -
VMRetirement/Defaultfor retirement.
In our case, we do not want to go through those default StateMachines, but to set up dedicated ones in the respective namespaces as follows :
-
Custom2/Custom2StateMachinefor provisioning. -
CustomRetirement/Custom2Retirefor retirement.
We intentionally choose to have non coherent naming policy between our provisioning and retirement Classes/Instances so to avoid confusion in that example.
Step 1 : Creating your StateMachines
Create your StateMachines classes and instances in one of your test domains. Don't forget populating your new classes with respective update_provision_status and update_retirement_status methods, each accepting a status parameter.
Here is an example of a test datastore snapshot with that step completed.

Step 2 : Create your tags
In that example we will need two tags per state machine as we want to drive provisioning and retirement, with both specific classes and instances. This is not an obligation as you may perfectly wish (in exemple) to use default retirement, omitting any retirement tag. As well you may use default Class values, therefore only specifying instance tags.
However, for full coverage example , we will create categories and tags for the class and the instance.
- For target classes, tag categories are
vm_prov_classfor provisioning,vm_ret_classfor retirement. Create them if you don't yet have them. - For target instances, tag categories are
vm_prov_instancefor provisioning,vm_ret_instancefor retirement. Same notice as before.
As a convention, we considered Classes and Instances to be named in CamelCase. (see previous naming convention discussion). The tags pointing at them will have to be *snake_case *due to tags restrictions in supported character sets so a conversion will be done. To point at Custom2StateMachine, the tag we need to create will require the value custom2_state_machine, and so forth.
The categories/tags to create in our case are :
vm_prov_class/custom2vm_prov_instance/custom2_state_machinevm_ret_class/custom_retirementvm_ret_instance/custom2_retire
Step 4 : Tag your catalog item.
Create a catalog item for VM provisioning. Run a test provisioning and retirement. It should currently go through default StateMachines (see thereafter for logs to check). Once you are sure you preserved default behaviors and that provisioning works correctly, tag your new item so to re-vectorize it to your custom provisioning, retirement or both state machines.
Here is an example catalog item, tagged to be re-vectorized on our 2 specific exemple state machines, modifying both class and instance names in each case :

Provision and retire a VM from this catalog item. Check it goes through your custom State Machines (see thereafter for logs to check).
Checking provisioning / retirement selection
Without entering implementation details that are covered later on, you may check for one method output in automation.log : launch_state_machine. This method does class and instance selections before launching appropriate StateMachine.
Here is an awk’ed output of launch_state_machine on our example service.
Provisioning output :
> <AEMethod launch_state_machine> ---> launch_state_machine starting
> <AEMethod launch_state_machine> --> Called with message : provision - class tag category : vm_prov_class
> <AEMethod launch_state_machine> --> got service_template : BrickCustomStateMachine - ID: 64000000000014
> <AEMethod launch_state_machine> --> Called with message : provision - instance tag category : vm_prov_instance
> <AEMethod launch_state_machine> --> got service_template : BrickCustomStateMachine - ID: 64000000000014
> <AEMethod launch_state_machine> --> Setting prov. state_machine class to: Custom2
> <AEMethod launch_state_machine> --> Target VM prov. StateMachine = /Infrastructure/VM/Provisioning/StateMachines/Custom2/Custom2StateMachine
> <AEMethod [/CustomStateMachines/Integration/CloudForms/CustomStateMachines/launch_state_machine]> Ending
Retirement output :
> <AEMethod launch_state_machine> ---> launch_state_machine starting
> <AEMethod launch_state_machine> --> Called with message : retire - class tag category : vm_ret_class
> <AEMethod launch_state_machine> --> got service_template : BrickCustomStateMachine - ID: 64000000000014
> <AEMethod launch_state_machine> --> Called with message : retire - instance tag category : vm_ret_instance
> <AEMethod launch_state_machine> --> got service_template : BrickCustomStateMachine - ID: 64000000000014
> <AEMethod launch_state_machine> --> Setting ret. state_machine class to: CustomRetirement
> <AEMethod launch_state_machine> --> Target VM ret. StateMachine with instance = /Infrastructure/VM/Retirement/StateMachines/CustomRetirement/Custom2Retire
> <AEMethod [/CustomStateMachines/Integration/CloudForms/CustomStateMachines/launch_state_machine]> Ending
As well, you may include a log output of the current state machine you are in from either your update_provision_status or update_retirement_status code within your custom state machines by adding the following at the beginning of those methods :
$evm.log(:info," ===> Current VM provisioning StateMachine : #{$evm.current_namespace}/#{$evm.current_class}/#{$evm.current_instance}")Implementation details
Default logic
As a request event is sent, one initial entry point used to select the appropriate StateMachine (for infrastructure) is under the class /Infrastructure/VM/Lifecycle for Infrastructure VMs or /Cloud/VM/Lifecycle for Cloud VMs.
The principle being the same for Infrastructure and Cloud VM provisioning or retirement, we will consider Infrastructure in the following discussion.
The Lifecycle class schema define an empty list of states, mainly relationships and methods.
This class contains two instances that are of interest in our case : Provisioning and Retirement . The third one, Migrate, do not enter in our current perimeter.
Provisioning
Here are the states used in Lifecycle/Provisioning instance :

At state RelationShip5 it calls an instance named from the user group name within /Infrastructure/VM/Provisioning/Profile class, transmitting the message get_state_machine.
By default, the only group that has an instance is EvmGroup-super_administrator with no specificities from the class schema, so in most cases it’s the .missing instance that will be called, identical as well to the class schema by default. In those instances the only step that is triggered on the get_state_machine message is the last one :

We then have the choice to intercept the default provisioning StateMachine election at two different stages, at the Lifecycle/Provisioning or at the Profile/.missing stage.
Retirement
Here is the state used in Lifecycle/Retirement default instance :

From that state we directly jump to the default retirement StateMachine.
Implementing custom StateMachine selection
To stay homogeneous in our implementation we decided to place our StateMachines selection and launching entry point at the same level for both provisioning and retirement. Therefore we will implement it at the Lifecycle/Provisioning step.
From provisioning standpoint it means the default feature of selecting a state machine based on the user group will be deactivated. So far I never seen it used nor did I see use cases for it to be used. Should it happen, then the presented logic based on a tag criteria could perfectly be adapted to match a group criteria.
The CustomStateMachines class
Logic is implemented at :
/CustomStateMachines/Integration/CloudForms/CustomStateMachines

This class contains two instances and one method. The two instances inherit all their values from the class schema except the following :
-
Instance
InfraStateMachine: Initialize basic paths and defaults for Infrastructure VM provisioning. Executelaunch_state_machine -
Instance
CloudStateMachine: Initialize basic paths and defaults for Cloud VM provisioning. Executelaunch_state_machine
The method is the same for provisioning and retirement, both for Cloud or Infrastructure VMs.
Method launch_state_machine : Will determine the state machine class to be called and will run it. This method have to be called with a message provision when provisioning, retire when retiring. If there are custom class or instance tags on the catalog item it will integrate them in the StateMachine path (translating from snake_case to CamelCase). If there are no such tag, it will use the default class or instance, or both, in place.
To avoid hard coding and improve maintenance and customization, the instance schema is somewhat complete but should not require modification except if you really wish it. Here is this schema from the InfraStateMachine instance.

We considered that values had to be set only when relevant to the message sent. Names are self explanatory but here is a short who_does_what :
-
[provision,retirement]_category: The tag categories to search for classes names. -
[prov,ret]_instance_cat: The tag categories to search for instances names. -
def_prov_*: The default MiQ values for provisioning (should not be changed). Note thatdef_prov_namespaceis different betweenInfraStateMachineandCloudStateMachine. -
def_ret_*: The default MiQ values for retirement (should not be changed) Note thatdef_ret_namespaceis different betweenInfraStateMachineandCloudStateMachine -
[prov,ret]_msg: The messages the instance accept (should not be changed)
Vectorizing StateMachines selection and launch
Logically, we modified the previously seen Lifecycle instances. They now contain only one state, so to call the appropriate CustomStateMachine selection. All messages are default create messages from class schema.
/CustomStateMachines/Infrastructure/VM/Lifecycle/Provisioning

/CustomStateMachines/Infrastructure/VM/Lifecycle/Retirement

/CustomStateMachines/Cloud/VM/Lifecycle/Provisioning

/CustomStateMachines/Cloud/VM/Lifecycle/Retirement

From now, as far as those four instances may be called for provisioning or retirement (meaning, they are not overloaded at some place) you will have dynamic custom state machines activated on all your enabled domains, whatever is their priority order.