CloudForms API extension: fast way

17:41 0 Comments

Sometimes it can be quite difficult to work with CloudForms API. Obviously, there is nothing complicated using it, but if you need to extend API to provide some additional service, you may spend some serious time to understand how it works.
There are several ways to extend existing API, today I will describe the fastest, which takes just a little time to realize.
This method is suitable only if you don't need to add new instances to CloudForms, but just want to collect some existing info in one place and pass it through API.
CloudForms API provides limited information about services, which wasn't enough for our case. We wanted to add some extra info, especially about Virtual Machines.
Firs of all describe new API instance in ${VMDB}/config/api.yml
---
  :extended_services:
    :description: Extended Service API
    :options:
    - :collection
    :methods: *70174834086080
:extended_services: name of our new API
- :collection means that it will be available directly, so our new API URL is {hostname}/api/extended_services
:methods: *70174834086080 is a hash of HTTP Methods available for collection. You can find all values in ${VMDB}/config/api.yml
---
:method:
  :names:
  - :get
  - :put
  - :post
  - :patch
  - :delete
  :sets:
    :g: &70174834086080
    - :get
    :gp: &70174834085860
    - :get
    - :post
    :gpd: &70174834085620
    - :get
    - :post
    - :delete
    :gpppd: &70174834084700
    - :get
    - :put
    - :post
    - :patch
    - :delete

In our case we need only GET method. Though POST method realization is in some way similar to GET, there are still some serious differences, which we will describe in future articles.
Then we need to write our new controller, which will actually do all the work ${VMDB}/app/controllers/api_controller/extended_services.rb
class ApiController
  module ExtendedServices

    def show_extended_services
 #@req[:c_id] is global variable, id of collection instance from url.
  #check if this param exists
 #and if not, render whole collection instead of one element
        if !@req[:c_id]
          render_extended_services_collection()
        else
          render_extended_services(@req[:c_id])
        end
    end

    def render_extended_services_collection()
      resp = Jbuilder.new
      my_services = find_filtered_api(Service, :all)
      resp.resources my_services do |my_service|
        resp.id my_service.id
        resp.name my_service.name
        resp.created_at my_service.created_at
        resp.template my_service.service_template_id
        resp.cpus my_service.aggregate_direct_vm_cpus
        resp.vms my_service.vms.size
        resp.memory my_service.aggregate_all_vm_memory
      end
      render :json => resp.target!
    end

    def  render_extended_services(id)
      my_service = find_by_id_filtered_api(Service, id)
      resp = Jbuilder.new
      resp.resources do
        resp.id id
        resp.name my_service.name
        resp.created_at my_service.created_at
        resp.vms my_service.vms do |vm|
          resp.id vm.id
          resp.name vm.name
          resp.state vm.normalized_state
          resp.cpu vm.num_cpu
          resp.used_storage vm.used_storage
          resp.snapshots vm.snapshots
        end
      end
    render :json => resp.target!
    end
  end
end
Thit is important, that one method called "show_"+{api name} because after /api/extended_services called, CloudForms looks exactly for method with this name and invokes it. Here all information that we need from service is collected and rendered.
All CloudForms api responses should be in JSON, we use Jbuilder to make JSON from our data.
We use custom methods find_filtered_api and find_by_id_filtered_api to find instances, user have access to. This methods are just slightly corrected methods find_filtered and find_by_id_filtered from app/controllers/application_controller.rb. We use basic ideas of this methods, but we apply all filters to @user global variable which stores API user. We save this methods in our custom api helper, so we can use them in future.
${VMDB}app/helpers/api_helper/custom_helper.rb
---
    def find_filtered_api(db, count, options={})
      user     =  User.find_by_userid(@auth_user)
      mfilters = user ? user.get_managed_filters   : []
      bfilters = user ? user.get_belongsto_filters : []
        $api_log.info(bfilters)
      if db.respond_to?(:find_filtered) && !mfilters.empty?
        result = db.find_tags_by_grouping(mfilters, :conditions => options[:conditions], :ns=>"*")
      else
        result = Rbac.search(:class => db, :conditions => options[:conditions], :userid => @auth_user, :results_format => :objects).first
      end

      result = MiqFilter.apply_belongsto_filters(result, bfilters) if db.respond_to?(:find_filtered) &&  result

      result
    end

    def find_by_id_filtered_api(db, id)
      raise "Invalid input" unless is_integer?(id)
      userid  = @auth_user
      unless db.where(:id => from_cid(id)).exists?
        msg = I18n.t("flash.record.selected_item_no_longer_exists", :model => ui_lookup(:model => db.to_s))
        raise msg
      end
      msg = "User '#{userid}' is not authorized to access '#{ui_lookup(:model=>db.to_s)}' record id '#{id}'"
      conditions = ["#{db.table_name}.id = ?", id]
      result = Rbac.search(:class => db, :conditions => conditions, :userid => userid, :results_format => :objects).first.first
      raise msg if result.nil?
      result
    end
---
Include new module in ${VMDB}/app/controllers/api_controller.rb
---
  include_concern "ExtendedServices"
Restart CFME:
$> cd /var/www/miq/vmdb/
$> rake evm:restart
That's it!
Now we can go to our newly created pages and see results
All collection {hostname}/api/extended_services
One service {hostname}/api/extended_services/57

Unknown

IBA Group, Minsk

0 comments: