Calling External Services

One of the powerful features of CloudForms/ManageIQ is Integration, or its ability to orchestrate and coordinate with external services as part of a workflow. There services might include a corporate IPAM solution, ticketing system, or a CMDB for example.

Kevin Morey's CloudFormsPOC code collection contains a useful set of sample Integration scripts that we can use and study.

screenshot

We typically use SOAP or RESTful APIs to access theses external services, and there are several Ruby Gems that make this easy for us, including Savon (SOAP client), RestClient, XmlSimple and Nokogiri (XML parsers), and Fog (a Ruby Cloud Services Library).

Calling a RESTful API Using the RestClient Gem

We have already seen an example of using the rest_client gem in Example - Customising VM Provisioning. Here is another example, contributed to the CloudFormsPOC Integration code by Carsten Clasohm, that creates a NetAPP volume using a REST call to NetApp's WFA (some lines have been omitted for brevity/clarity):

require 'rest_client'
require 'nokogiri'

def call_netapp(action, ref=nil, body_type=:xml, body=nil)
  servername = $evm.object['servername']
  username = $evm.object['username']
  password = $evm.object.decrypt('password')
  url = "https://#{servername}/rest/workflows"+"#{ref}"

  params = {
    :method => action,
    :url => url,
    :verify_ssl => false,
    :user => username,
    :password => password,
    :headers => { :content_type => body_type, 
                  :accept => :xml, 
                  :authorization => "Basic #{password}" },
    :payload => body
  }

  response = nil
  begin
    response = RestClient::Request.new(params).execute
  rescue => e
    msg = "#{e}, #{e.response}"
    error("Error calling NetApp: #{msg}")
  end
  unless response.code == 200 || response.code == 201
    raise "Failure <- NetApp Response:<#{response.code}>"
  end
  results = Nokogiri::XML.parse(response)
  return results
end
...
body_xml = "<workflowInput><userInputValues>"
body_xml += "<userInputEntry key='ClusterName' value='#{primary_cluster_name}'/>"
body_xml += "<userInputEntry key='VserverName' value='#{primary_vserver_name}'/>"
body_xml += "<userInputEntry key='VolumeName' value='#{volume_name}'/>"
body_xml += "<userInputEntry key='VolumeSizeGB' value='#{volume_size}'/>"
body_xml += "<userInputEntry key='ExportPolicyName' value='#{export_policy_name}'/>"
unless mirror_cluster_name.blank?
  body_xml += "<userInputEntry key='SnapMirrorDestinationClusterName' value='#{mirror_cluster_name}'/>"
  body_xml += "<userInputEntry key='SnapMirrorDestinationVserverName' value='#{mirror_vserver_name}'/>"
  body_xml += "<userInputEntry key='SnapMirrorPolicy' value='DPDefault'/>"
end
body_xml += "</userInputValues></workflowInput>"

workflow_execute = call_netapp(:post, ref="/#{workflow_guid}/jobs", :xml, body=body_xml)
job_id = workflow_execute.xpath('/job/@jobId')[0].content
...

Calling a SOAP API Using the Savon Gem

The following snippet is from the CloudFormsPOC Integration library, and shows an example of making a SOAP call to an f5 BIG-IP load balancer to add an IP address to a pool (some lines have been omitted for brevity/clarity):

  def call_F5_Pool(soap_action, body_hash=nil)
    servername = nil || $evm.object['servername']
    username = nil || $evm.object['username']
    password = nil || $evm.object.decrypt('password')

    # require necessary gems
    require "rubygems"
    gem 'savon', '=2.3.3'
    require "savon"
    require 'httpi'

    # configure httpi gem to reduce verbose logging
    HTTPI.log_level = :info # changing the log level
    HTTPI.log       = false # diable HTTPI logging
    HTTPI.adapter   = :net_http # [:httpclient, :curb, :net_http]

    # configure savon gem
    soap = Savon.client do |s|
      s.wsdl "https://#{servername}/iControl/iControlPortal.cgi?WSDL=LocalLB.Pool"
      s.basic_auth [username, password]
      s.ssl_verify_mode :none
      s.endpoint "https://#{servername}/iControl/iControlPortal.cgi"
      s.namespace 'urn:iControl:LocalLB/Pool'
      s.env_namespace :soapenv
      s.namespace_identifier :pool
      s.raise_errors false
      s.convert_request_keys_to :none
      s.log_level :error
      s.log false
    end

    response = soap.call soap_action do |s|
      s.message body_hash unless body_hash.nil?
    end

    # Convert xml response to a hash
    return response.to_hash["#{soap_action}_response".to_sym][:return]
  end
  ...
  vm.ipaddresses.each do |vm_ipaddress|
    body_hash = {}
    body_hash[:pool_names] = {:item => [f5_pool]}
    body_hash[:members] = [{:items => 
                            { :member => 
                               {:address => vm_ipaddress, 
                                :port => f5_port} 
                             } 
                           }]
    # call f5 and return a hash of pool names
    f5_return = call_F5_Pool(:add_member, body_hash)
  end

Calling an OpenStack API using the Fog Gem

The fog gem is a multi-purpose cloud services library that supports connectivity to a number of cloud providers.

The follow code is an example of using the fog gem to retrieve OpenStack networks from Neutron, and present them as a dynamic drop-down dialog list. The code filters networks that match a tenant's name, and assumes that the CloudForms user has a Tenant tag containing the same name:

require 'fog'
begin
  tenant_name = $evm.root['user'].current_group.tags(:tenant).first
  $evm.log(:info, "Tenant name: #{tenant_name}")

  dialog_field = $evm.object
  dialog_field["sort_by"] = "value"
  dialog_field["data_type"] = "string"
  openstack_networks = {}
  openstack_networks[nil] = '< Select >'
  ems = $evm.vmdb('ems').find_by_name("OSP7 PackStack Core01")
  raise "ems not found" if ems.nil?

  neutron_service = Fog::Network.new({
    :provider => 'OpenStack',
    :openstack_api_key => ems.authentication_password,
    :openstack_username => ems.authentication_userid,
    :openstack_auth_url => "http://#{ems.hostname}:35357/v2.0/tokens",
    :openstack_tenant => tenant_name
  })

  keystone_service = Fog::Identity.new({
    :provider => 'OpenStack',
    :openstack_api_key => ems.authentication_password,
    :openstack_username => ems.authentication_userid,
    :openstack_auth_url => "http://#{ems.hostname}:35357/v2.0/tokens",
    :openstack_tenant => tenant_name
  })

  tenant_id = keystone_service.current_tenant["id"]
  $evm.log(:info, "Tenant ID: #{tenant_id}")
  networks = neutron_service.networks.all
  networks.each do |network|
    $evm.log(:info, "Found network #{network.inspect}")
    if network.tenant_id == tenant_id
      network_id = $evm.vmdb('CloudNetwork').find_by_ems_ref(network.id)
      openstack_networks[network_id] = network.name
    end
  end

  dialog_field["values"] = openstack_networks
  #
  #
  #
  $evm.log("info", "get_networks Method Ended")
  exit MIQ_OK

rescue => err
  $evm.log(:error, "[#{err}]\n#{err.backtrace.join("\n")}")
  exit MIQ_STOP
end

Reading from a MySQL Database Using the Mysql Gem

The following code snippet shows an example of using the mysql gem to connect to a MySQL-based CMDB, to extract project codes and create tags from them:

require 'rubygems'
require 'mysql'

begin
  server = $evm.object['server']
  username = $evm.object['username']
  password = $evm.object.decrypt('password')
  database = $evm.object['database']

  con = Mysql.new(server, username, password, database)

  unless $evm.execute('category_exists?', "project_code")
    $evm.execute('category_create', :name => "project_code", 
                                       :single_value => true, 
                                       :description => "Project Code")
  end
  #
  # Force UTF_8 return
  #
  con.query('SET NAMES utf8')
  #
  # Retrieve the list of Project Codes from the CMDB
  #
  query_results = con.query('SELECT description,code FROM projectcodes')
  query_results.each do |record|
    tag_name = record[1]
    tag_display_name = record[0].force_encoding(Encoding::UTF_8)

    unless $evm.execute('tag_exists?', 'project_code', tag_name)
      $evm.execute('tag_create', "project_code", :name => tag_name, 
                                                   :description => tag_display_name)
    end
  end
end
rescue Mysql::Error => e
  puts e.errno
  puts e.error
ensure
  con.close if con
end

These examples illustrate the tremendous flexibility that CloudForms/ManageIQ has to integrate with other Enterprise components. For example we can create State Machine based workflows that interface with help-desk ticketing systems, CMDBs, Corporate IPAM solutions, Enterprise Storage, Directory Services, configuration management systems, firewalls, or network load balancers.

results matching ""

    No results matching ""