Service Hierarchies
We have seen how service catalogs made up of catalog items and bundles can simplify the process of ordering infrastructure or cloud instance and virtual machines. Simplicity of ordering is not the only benefit of services however.
When we order one or more virtual machines from a service catalog, a new service is created for us that appears in All Services in the WebUI. This service gives us a useful summary of its resources in the Totals for Service VMs section. We can use this feature to extend the utility of services into tracking and organising resources. We could, for example, use a service to represent a project comprising many dozens of virtual machines. We would be able to see the total virtual machine resource consumption for the entire project in a single place in the WebUI.
In line with this organisational use, we can arrange services in hierarchies for further convenience (see A service hierarchy).
In this example we have three child services, representing the three tiers of our simple intranet platform. The database tier shows the single server making up the database tier of our architecture.
The middleware tier shows the two servers making up the middleware tier of our architecture.
The web tier shows the four servers making up the web tier of our architecture.
When we view the parent service, we see that it contains details of all child services, including the cumulative CPU, memory and disk counts (see Parent service view).
Organising Our Services
To make maximum use of service hierarchies, it is useful to be able to create empty services, and to be able to move both services and VMs into existing services.
Creating an Empty Service
We could create a new service directly from automation, using the lines:
new_service = $evm.vmdb('service').create(:name => "My New Service")
new_service.display = true
For this example though, we’ll create our new empty service from a service catalog.
State machine
First we’ll copy ManageIQ/Service/Provisioning/StateMachines/ServiceProvision_Template/default into our own Domain, and rename it EmptyService. We’ll add a pre5 relationship to a new instance that we’ll create, called /Service/Provisioning/StateMachines/Methods/rename_service (see Fields of the EmptyService state machine).
Method
The pre5 stage of this state machine is a relationship to a rename_service instance. This instance calls a rename_service method containing the following code:
begin
service_template_provision_task = $evm.root['service_template_provision_task']
service = service_template_provision_task.destination
dialog_options = service_template_provision_task.dialog_options
if dialog_options.has_key? 'dialog_service_name'
service.name = "#{dialog_options['dialog_service_name']}"
end
if dialog_options.has_key? 'dialog_service_description'
service.description = "#{dialog_options['dialog_service_description']}"
end
$evm.root['ae_result'] = 'ok'
exit MIQ_OK
rescue => err
$evm.log(:error, "[#{err}]\n#{err.backtrace.join("\n")}")
$evm.root['ae_result'] = 'error'
$evm.root['ae_reason'] = "Error: #{err.message}"
exit MIQ_ERROR
end
Service dialog
We create a simple service dialog called "New Service" with element names service_name and service_description (see Service dialog).
Putting it all together
Finally we assemble all of these parts by creating a new service catalog called General Services, a new catalog item of type Generic called Empty Service (see The completed "Empty Service" service catalog item).
We can order from this service catalog item to create our new empty services.
Adding VMs and Services to Existing Services
We’ll provide the ability to move both services and virtual machines into existing services, from a button. The button will present a drop-down list of existing services that we can add as a new parent service (see Listing available services in a dynamic drop-down).
Adding the Button
As before, the process of adding a button involves the creation of the button dialog, and a button script. For this example however our dialog will contain a dynamic drop-down list, so we must create a dynamic element method as well to populate this list.
Button Dialog
We create a simple button dialog with a dynamic drop-down element named service (see Button Dialog).
Dialog element method
The dynamic drop-down element in the service dialog calls a method called list_services. We only wish to display a service in the drop-down list if the user has permissions to see it via their tenant membership and role-based access control (RBAC) filter. We define three methods; get_visible_tenant_ids
to get our tenant and any child tenant IDs; get_current_group_rbac_array
to retrieve a user’s RBAC filter array, and service_visible?
to check that a service has a tag that matches the filter. The code is as follows:
def get_visible_tenant_ids
tenant_ancestry = []
tenant_ancestry << $evm.root['tenant'].id
$evm.vmdb(:tenant).all.each do |tenant|
unless tenant.ancestry.nil?
ancestors = tenant.ancestry.split('/')
if ancestors.include?($evm.root['tenant'].id.to_s)
tenant_ancestry << tenant.id
end
end
end
tenant_ancestry
end
def get_current_group_rbac_array(rbac_array=[])
user = $evm.root['user']
unless user.current_group.filters.blank?
user.current_group.filters['managed'].flatten.each do |filter|
next unless /(?<category>\w*)\/(?<tag>\w*)$/i =~ filter
rbac_array << {category => tag}
end
end
rbac_array
end
def service_visible?(visible_tenants, rbac_array, service)
visible = false
$evm.log(:info, "Evaluating Service #{service.name}")
if visible_tenants.include?(service.tenant.id)
if rbac_array.length.zero?
visible = true
else
rbac_array.each do |rbac_hash|
rbac_hash.each do |category, tag|
if service.tagged_with?(category, tag)
visible = true
end
end
end
end
end
visible
end
When we enumerate the services, we check on visibility to the user before adding to the drop-down list:
rbac_array = get_current_group_rbac_array
visible_tenants = get_visible_tenant_ids
values_hash = {}
visible_services = []
$evm.vmdb(:service).all.each do |service|
if service['display']
if service_visible?(visible_tenants, rbac_array, service)
visible_services << service
end
end
end
if visible_services.length > 0
if visible_services.length > 1
values_hash['!'] = '-- select from list --'
end
visible_services.each do |service|
values_hash[service.id] = service.name
end
else
values_hash['!'] = 'No services are available'
end
Here we use a simple technique of keeping the string "-- select from list --" at the top of the list, by using a key string of "!" which is the first ASCII printable nonwhitespace character.
Button method
The main instance and method called from the button are each called add_to_service. This method adds the current virtual machine or service, into the service selected from the drop-down list. As we wish to be able to call this from a button on either a Service object type or a VM and instance object type, we identify our context using $evm.root['vmdb_object_type']
.
If we are adding a virtual machine to an existing service, we should allow for the fact that the virtual machine might itself have been provisioned from a service. We detect any existing service membership, and if the old service is empty after we move the virtual machine, we delete the service from the VMDB:
begin
new_service_id = $evm.root['dialog_service']
new_service = $evm.vmdb('service', new_service_id) rescue nil
if new_service.nil?
$evm.log(:error, "Can't find service with ID: #{new_service_id}")
exit MIQ_ERROR
else
case $evm.root['vmdb_object_type']
when 'service'
$evm.log(:info, "Adding Service #{$evm.root['service'].name} to #{new_service.name}")
$evm.root['service'].new_service = new_service
when 'vm'
vm = $evm.root['vm']
#
# See if the VM is already part of a service
#
unless vm.service.nil?
old_service = vm.service
vm.remove_from_service
if old_service.v_total_vms.zero?
$evm.log(:info, "Old service #{old_service.name} is now empty, removing it from VMDB")
old_service.remove_from_vmdb
end
end
$evm.log(:info, "Adding VM #{vm.name} to #{new_service.name}")
vm.add_to_service(new_service)
#
# Set the VM's owner and group to be the same as the new service
#
unless new_service.evm_owner_id.nil?
vm.owner = $evm.vmdb(:user).find_by_id(new_service.evm_owner_id)
end
unless new_service.miq_group_id.nil?
vm.group = $evm.vmdb(:miq_group).find_by_id(new_service.miq_group_id)
end
end
end
exit MIQ_OK
rescue => err
$evm.log(:error, "[#{err}]\n#{err.backtrace.join("\n")}")
exit MIQ_ERROR
end
The scripts in this chapter are available here
Putting it all together
Finally we create two Add to Service buttons, one on a Service object type, and one on a VM and Instance object type. We can go ahead and organise our service hierarchies.
Note
|
Exercise Filter the list of services presented in the drop-down to remove the current service - we would never wish to add a service as its own parent. |
Summary
Organising our services in this way changes the way that we think about our cloud or virtual infrastructure. We start to think in terms of service workloads, rather than individual virtual machines or instances. We can start to work in a more "cloudy" way, where we treat our virtual machines as anonymous entities, and scale out or scale back according to point-in-time application demand.
We can also use service bundles and hierachies of bundles to keep track of the resources in projects and subprojects. This can help from an organisational point of view, for example we can tag services, and our method to add a virtual machine to a service can propagate any service tags to the virtual machine. In this way we can assign project-related chargeback costs to the tagged VMs, or apply WebUI display filters that display project resources.