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).
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.
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.
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/RenameService (see Schema of the EmptyService state machine).
The pre5 stage of this state machine is a relationship to a RenameService 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
We create a simple service dialog called "New Service" with element names service_name and service_description (see Service dialog).
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.
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).
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.
We create a simple button dialog with a dynamic drop-down element named service (see Button Dialog).
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 role-based access control (RBAC) filter. We define two methods; 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:
def get_current_group_rbac_array(user, rbac_array=[])
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?(rbac_array, service)
$evm.log(:info, "Evaluating Service #{service.name}")
if rbac_array.length.zero?
$evm.log(:info, "No Filter, service: #{service.name} is visible to this user")
return true
else
rbac_array.each do |rbac_hash|
rbac_hash.each do |category, tag|
if service.tagged_with?(category, tag)
$evm.log(:info, "Service: #{service.name} is visible to this user") return true
end
end
end
false
end
end
When we enumerate the services, we check on visibility to the user before adding to the drop-down list:
$evm.vmdb(:service).find(:all).each do |service|
if service['display']
$evm.log(:info, "Found service: #{service.name}")
if service_visible?(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.
The main instance and method called from the button are called AddToService and 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
parent_service_id = $evm.root['dialog_service']
parent_service = $evm.vmdb('service').find_by_id(parent_service_id)
if parent_service.nil?
$evm.log(:error, "Can't find service with ID: #{parent_service_id}")
exit MIQ_ERROR
else
case $evm.root['vmdb_object_type']
when 'service'
$evm.log(:info, "Adding Service #{$evm.root['service'].name} to \
#{parent_service.name}")
$evm.root['service'].parent_service = parent_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?
old_service.remove_from_vmdb
end
end
$evm.log(:info, "Adding VM #{vm.name} to #{parent_service.name}")
vm.add_to_service(parent_service)
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
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. |
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.