$evm and the Workspace
When we program with the CloudForms/ManageIQ Automation Engine, we access all of the Automation objects through a single $evm
variable. This is sometimes referred to as the Workspace.
This variable is actually an instance of an MiqAeService
object (defined in /var/www/miq/vmdb/lib/miq_automation_engine/engine/miq_ae_service.rb
on the appliance), which contains over forty methods. In practice we generally only use a few of these methods, most commonly:
$evm.root
$evm.object
$evm.current (this is equivalent to calling $evm.object(nil))
$evm.log
$evm.vmdb
$evm.execute
$evm.instantiate
We can look at these methods in more detail.
$evm.log
$evm.log
is a simple method that we've used already. It writes a message to automation.log
, and accepts two arguments, a log level, and the text string to write. The log level can be written as a Ruby symbol (e.g. :info
, :warn
), or as a text string (e.g. "info", "warn").
$evm.root
$evm.root
is the method that returns to us the root object in the workspace (environment, variables, linked objects etc.). This is the Instance whose invocation took us into the Automation Engine. From $evm.root
we can access other Service Model objects such as $evm.root['vm']
, $evm.root['user']
, or $evm.root['miq_request']
, (the actual objects available depend on the context of the Automation tasks that we are performing).
$evm.root
contains a lot of useful information that we use programatically to establish our running context (for example to see if we've been called by an API call or from a button, e.g.
$evm.root['vmdb_object_type'] = vm (type: String)
...
$evm.root['ae_provider_category'] = infrastructure (type: String)
...
$evm.root.namespace = ManageIQ/SYSTEM (type: String)
$evm.root['object_name'] = Request (type: String)
$evm.root['request'] = Call_Instance (type: String)
$evm.root['instance'] = ObjectWalker (type: String)
(see also Investigative Debugging)
$evm.root
also contains any variables that were defined on our entry into the Automation engine, such as the $evm.root['dialog*']
variables that were defined from our service dialog.
$evm.object, $evm.current and $evm.parent
As we saw, $evm.root
returns to us the object representing the Instance that was launched when we entered Automate. Many Instances have schemas that contain Relationships to other Instances, and as each Relationship is followed, a new child object is created under the calling object to represent the called Instance. Fortunately we can access any of the objects in this parent-child hierarchy using $evm.object
.
Calling $evm.object
with no arguments returns the currently instantiated/running Instance. As Automation scripters we can think of this as "our currently running code", and this can also be accessed using the alias $evm.current
. When we wanted to access our schema variable username, we accessed it using $evm.object['username']
.
We can access our parent object (the one that called us) using $evm.object("..")
, or the alias $evm.parent
.
Fact: $evm.root
is actually an alias for $evm.object("/")
When we ran our first example script, HelloWorld (from Simulation), we specified an entry point of /System/Process/Request
, and our Request was to an Instance called Call_Instance
. We passed to this the Namespace, Class and Instance that we wanted it to run (via a Relationship).
This would have resulted in an object hierarchy (when viewed from the hello_world Method) as follows:
--- object hierarchy ---
$evm.root = /ManageIQ/SYSTEM/PROCESS/Request
$evm.parent = /ManageIQ/System/Request/Call_Instance
$evm.object = /ACME/General/Methods/HelloWorld
$evm.vmdb
$evm.vmdb
is a useful method that can be used to retrieve any Service Model object (see The MiqAeService* Model). The method can be called with one or two arguments, as follows.
When called with a single argument, the method returns the generic Service Model object type, and we can use any of the Rails helper methods (see A Little Rails Knowledge) to search by database column name, i.e.
vm = $evm.vmdb('vm').find_by_id(vm_id)
clusters = $evm.vmdb(:EmsCluster).find(:all)
$evm.vmdb(:VmOrTemplate).find_each do | vm |
The service model object name can be specified in CamelCase (e.g. 'AvailabilityZone') or snake_case (e.g. 'availability_zone'), and can be a string or symbol.
When called with two arguments, the second argument should be the Service Model ID to search for, i.e.
owner = $evm.vmdb('user', evm_owner_id)
We can also use more advanced query syntax to return results based on multiple conditions, i.e.
$evm.vmdb('CloudTenant').find(:first,
:conditions => ["ems_id = ? AND name = ?", src_ems_id, tenant_name])
Question: What's the difference between 'vm' (:Vm
) and 'vm_or_template' (:VmOrTemplate
)?
Answer: Searching for a 'vm_or_template' (MiqAeServiceVmOrTemplate
) object will return VMs or Templates that satisfy the search criteria, whereas search for a 'vm' object (MiqAeServiceVm
) will only return VMs. Less obviously though, MiqAeServiceVm
is a subclass of MiqAeServiceVmOrTemplate
that adds 2 additional methods that are not relevant for templates: .add_to_service
and .remove_from_service
.
Both MiqAeServiceVmOrTemplate
and MiqAeServiceVm
have a boolean attribute template, that is true for a VMware/RHEV Template, and false for a VM.
$evm.execute
We can use $evm.execute
to call a method from /var/www/miq/vmdb/lib/miq_automation_engine/service_models/miq_ae_service_methods.rb
. The usable methods are:
send_email
snmp_trap_v1
snmp_trap_v2
category_exists?
category_create
tag_exists?
tag_create
netapp_create_datastore
netapp_destroy_datastore
service_now_eccq_insert
service_now_task_get_records
service_now_task_update
service_now_task_service
create_provision_request
create_automation_request
For example:
unless $evm.execute('tag_exists?', 'cost_centre', '3376')
$evm.execute('tag_create', "cost_centre", :name => '3376', :description => '3376')
end
or
to = '[email protected]'
from = '[email protected]'
subject = 'Test Message'
body = 'What an awesome cloud management product!'
$evm.execute(:send_email, to, from, subject, body)
The create_automation_request
method is new with CloudForms 4.0, and it enables us to chain Automation requests together. This is also very useful when we wish to explicitly launch an automation task in a different zone to the one that our currently running script resides in.
options = {}
options[:namespace] = 'Stuff'
options[:class_name] = 'Methods'
options[:instance_name] = 'MyInstance'
options[:user_id] = $evm.vmdb(:user).find_by_userid('pemcg').id
# options[:attrs] = attrs
# options[:miq_zone] = zone
approve = true
$evm.execute('create_automation_request', options, 'admin', approve)
$evm.instantiate
We can use $evm.instantiate
to launch another Automation Instance programmatically from a running method, by specifying its URI within the Automate namespace e.g.
$evm.instantiate('/Discovery/Methods/ObjectWalker')
Instances called in this way execute synchronously, so the calling method waits for completion before continuing. The called Instance also appears as a child object of the caller (it sees the caller as its $evm.parent
).