# # Copyright (C) 2010 Red Hat, Inc. # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. The # ASF licenses this file to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance with the # License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. require 'string' module DeltaCloud class BaseObjectParamError < Exception; end class NoHandlerForMethod < Exception; end # BaseObject model basically provide the basic operation around # REST model, like defining a links between different objects, # element with text values, or collection of these elements class BaseObject attr_reader :id, :url, :client, :base_name attr_reader :objects alias :uri :url # For initializing new object you require to set # id, url, client and name attribute. def initialize(opts={}, &block) @id, @url, @client, @base_name = opts[:id], opts[:url], opts[:client], opts[:name] @objects = [] raise BaseObjectParamError if @id.nil? or @url.nil? or @client.nil? or @base_name.nil? yield self if block_given? end # This method add link to another object in REST model # XML syntax: def add_link!(object_name, id) @objects << { :type => :link, :method_name => object_name.sanitize, :id => id } @objects << { :type => :text, :method_name => "#{object_name.sanitize}_id", :value => id } end # Method add property for hardware profile def add_hwp_property!(name, property, type) hwp_property=case type when :float then DeltaCloud::HWP::FloatProperty.new(property, name) when :integer then DeltaCloud::HWP::Property.new(property, name) end @objects << { :type => :property, :method_name => name.sanitize, :property => hwp_property } end # This method define text object in REST model # XML syntax: Instance 1 def add_text!(object_name, value) @objects << { :type => :text, :method_name => object_name.sanitize, :value => value } end # This method define collection of text elements inside REST model # XML syntax: #
127.0.0.1
#
127.0.0.2
#
def add_collection!(collection_name, values=[]) @objects << { :type => :collection, :method_name => collection_name.sanitize, :values => values } end # Basic method hander. This define a way how value from property # will be returned def method_handler(m, args=[]) case m[:type] when :link then return @client.send(m[:method_name].singularize, m[:id]) when :text then return m[:value] when :property then return m[:property] when :collection then return m[:values] else raise NoHandlerForMethod end end def method_missing(method_name, *args) # First of all search throught array for method name m = search_for_method(method_name) if m.nil? warn "[WARNING] Method unsupported by API: '#{self.class}.#{method_name}(#{args.inspect})'" return nil else # Call appropriate handler for method method_handler(m, args) end end private def search_for_method(name) @objects.detect { |o| o[:method_name] == "#{name}" } end end class ActionObject < BaseObject def initialize(opts={}, &block) super(opts) @action_urls = opts[:action_urls] || [] @actions = [] end # This trigger is called right after action. # This method does nothing inside ActionObject # but it can be redifined and used in meta-programming def action_trigger(action) end def add_action_link!(id, link) m = { :type => :action_link, :method_name => "#{link['rel'].sanitize}!", :id => id, :href => link['href'], :rel => link['rel'].sanitize, :method => link['method'].sanitize } @objects << m @actions << [m[:rel], m[:href]] @action_urls << m[:href] end def actions @objects.inject([]) do |result, item| result << [item[:rel], item[:href]] if item[:type].eql?(:action_link) result end end def action_urls actions.collect { |a| a.last } end alias :base_method_handler :method_handler # First call BaseObject method handler, # then, if not method found try ActionObject handler def method_handler(m, args=[]) begin base_method_handler(m, args) rescue NoHandlerForMethod case m[:type] when :action_link then do_action(m, args) else raise NoHandlerForMethod end end end private def do_action(m, args) args = args.first || {} method = m[:method].to_sym @client.request(method, m[:href], method == :get ? args : {}, method == :get ? {} : args) action_trigger(m[:rel]) end end class StatefulObject < ActionObject attr_reader :state def initialize(opts={}, &block) super(opts) @state = opts[:initial_state] || '' add_default_states! end def add_default_states! @objects << { :method_name => 'stopped?', :type => :state, :state => 'STOPPED' } @objects << { :method_name => 'running?', :type => :state, :state => 'RUNNING' } @objects << { :method_name => 'pending?', :type => :state, :state => 'PENDING' } @objects << { :method_name => 'shutting_down?', :type => :state, :state => 'SHUTTING_DOWN' } end def action_trigger(action) # Refresh object state after action @new_state_object = @client.send(self.base_name, self.id) @state = @new_state_object.state self.update_actions! end alias :action_method_handler :method_handler def method_handler(m, args=[]) begin action_method_handler(m, args) rescue NoHandlerForMethod case m[:type] when :state then evaluate_state(m[:state], @state) else raise NoHandlerForMethod end end end # private def evaluate_state(method_state, current_state) method_state.eql?(current_state) end def action_objects @objects.select { |o| o[:type] == :action_link } end def update_actions! new_actions = @new_state_object.action_objects @objects.reject! { |o| o[:type] == :action_link } @objects = (@objects + new_actions) end end def self.add_class(name, parent=:base) parent = parent.to_s parent_class = "#{parent.classify}Object" @defined_classes ||= [] class_name = "#{parent.classify}::#{name.classify}" unless @defined_classes.include?(class_name) DeltaCloud::API.class_eval("class #{class_name} < DeltaCloud::#{parent_class}; end") @defined_classes << class_name end DeltaCloud::API.const_get(parent.classify).const_get(name.classify) end def self.guess_model_type(response) response = Nokogiri::XML(response.to_s) return :action if ((response/'//actions').length == 1) and ((response/'//state').length == 0) return :stateful if ((response/'//actions').length == 1) and ((response/'//state').length == 1) return :base end class API class Action; end class Base; end class Stateful; end end end