incubator-deltacloud-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Ladislav Martincik <lmart...@redhat.com>
Subject Re: [PATCH core 2/4] Fixed xml_to_class method in client
Date Mon, 18 Oct 2010 11:19:20 GMT
See comments, below:

----- mfojtik@redhat.com wrote:

> From: Michal Fojtik <mfojtik@redhat.com>
> 
> ---
>  client/lib/base_object.rb              |  281 +++++++++++++++++++++
>  client/lib/deltacloud.rb               |  434
> ++++++++------------------------
>  client/lib/documentation.rb            |  136 ++++-------
>  client/lib/hwp_properties.rb           |   64 +++++
>  client/lib/instance_state.rb           |   29 +++
>  client/lib/string.rb                   |   53 ++++
>  client/specs/hardware_profiles_spec.rb |    2 +-
>  client/specs/instances_spec.rb         |    4 +-
>  8 files changed, 589 insertions(+), 414 deletions(-)
>  create mode 100644 client/lib/base_object.rb
>  create mode 100644 client/lib/hwp_properties.rb
>  create mode 100644 client/lib/instance_state.rb
>  create mode 100644 client/lib/string.rb
> 
> diff --git a/client/lib/base_object.rb b/client/lib/base_object.rb
> new file mode 100644
> index 0000000..b269bb8
> --- /dev/null
> +++ b/client/lib/base_object.rb
> @@ -0,0 +1,281 @@
> +#
> +# 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 'lib/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: <link rel="destroy"
> href="http://localhost/api/resource" method="post"/>
> +      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: <name>Instance 1</name>
> +      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: <addresses>
> +      #               <address>127.0.0.1</address>
> +      #               <address>127.0.0.2</address>
> +      #             </addresses>
> +      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

In the method_missing() method there's no need for 'else' statement, because you're calling
return before it. Also I think you can write just 'return' instead of 'return nil'.

> +
> +      private
> +
> +      def search_for_method(name)
> +        @objects.select { |o| o[:method_name] == "#{name}" }.first
> +      end

Doing 'select' and than 'first' is better to do with method 'detect' or 'find' from Enumerable.
It's faster.

> +
> +    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.select {|o| o[:type].eql?(:action_link) }.collect {
> |o| [o[:rel], o[:href]] }
> +      end

Here with 'select' and 'collect' you're iterating 2x. You could use just 'inject' to do one
pass.

> +
> +      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)
> +            else raise NoHandlerForMethod
> +          end
> +        end
> +      end
> +
> +      private
> +
> +      def do_action(m)
> +        @client.request(:"#{m[:method]}", m[:href], {}, {})
> +        action_trigger(m[:rel])
> +      end
> +
> +    end
> +
> +    class StateFullObject < 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_class = case parent
> +        when :base then BaseObject
> +        when :action then ActionObject
> +        when :state then StateFullObject
> +      end
> +      begin
> +        return API.const_get(name.classify)
> +      rescue NameError
> +        API.module_eval("class #{name.classify} <
> #{parent_class.to_s}; end")
> +        new_class = API.const_get(name.classify)
> +        @defined_classes ||= []
> +        @defined_classes << new_class
> +        new_class
> +      end
> +    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 :state if ((response/'//actions').length == 1) and
> ((response/'//state').length == 1)
> +      return :base
> +    end
> +
> +end
> diff --git a/client/lib/deltacloud.rb b/client/lib/deltacloud.rb
> index e44556f..5c224e5 100644
> --- a/client/lib/deltacloud.rb
> +++ b/client/lib/deltacloud.rb
> @@ -20,6 +20,10 @@ require 'nokogiri'
>  require 'rest_client'
>  require 'base64'
>  require 'logger'
> +require 'lib/hwp_properties'
> +require 'lib/instance_state'
> +require 'lib/documentation'
> +require 'lib/base_object'
>  
>  module DeltaCloud
>  
> @@ -56,27 +60,11 @@ module DeltaCloud
>      API.new(nil, nil, url).driver_name
>    end
>  
> -  def self.define_class(name)
> -    @defined_classes ||= []
> -    if @defined_classes.include?(name)
> -      self.module_eval("API::#{name}")
> -    else
> -      @defined_classes << name unless
> @defined_classes.include?(name)
> -      API.const_set(name, Class.new)
> -    end
> -  end
> -
> -  def self.classes
> -    @defined_classes || []
> -  end
> -
>    class API
> -    attr_accessor :logger
>      attr_reader   :api_uri, :driver_name, :api_version, :features,
> :entry_points
>  
>      def initialize(user_name, password, api_url, opts={}, &block)
>        opts[:version] = true
> -      @logger = opts[:verbose] ? Logger.new(STDERR) : []
>        @username, @password = user_name, password
>        @api_uri = URI.parse(api_url)
>        @features, @entry_points = {}, {}
> @@ -101,147 +89,91 @@ module DeltaCloud
>      # Define methods based on 'rel' attribute in entry point
>      # Two methods are declared: 'images' and 'image'
>      def declare_entry_points_methods(entry_points)
> -      logger = @logger
> +      
>        API.instance_eval do
>          entry_points.keys.select {|k|
> [:instance_states].include?(k)==false }.each do |model|
> +
>            define_method model do |*args|
>              request(:get, entry_points[model], args.first) do
> |response|
> -              # Define a new class based on model name
> -              c = DeltaCloud.define_class("#{model.to_s.classify}")
> -              # Create collection from index operation
> -              base_object_collection(c, model, response)
> +              base_object_collection(model, response)
>              end
>            end
> -          logger << "[API] Added method #{model}\n"
> +          
>            define_method :"#{model.to_s.singularize}" do |*args|
>              request(:get, "#{entry_points[model]}/#{args[0]}") do
> |response|
> -              # Define a new class based on model name
> -              c = DeltaCloud.define_class("#{model.to_s.classify}")
> -              # Build class for returned object
> -              base_object(c, model, response)
> +              base_object(model, response)
>              end
>            end
> -          logger << "[API] Added method #{model.to_s.singularize}\n"
> +          
>            define_method :"fetch_#{model.to_s.singularize}" do |url|
>              id = url.grep(/\/#{model}\/(.*)$/)
>              self.send(model.to_s.singularize.to_sym, $1)
>            end
> +
>          end
>        end
>      end
>  
> -    def base_object_collection(c, model, response)
> -      collection = []
> -     
> Nokogiri::XML(response).xpath("#{model}/#{model.to_s.singularize}").each
> do |item|
> -        c.instance_eval do
> -          attr_accessor :id
> -          attr_accessor :uri
> -        end
> -        collection << xml_to_class(c, item)
> +    def base_object_collection(model, response)
> +     
> Nokogiri::XML(response).xpath("#{model}/#{model.to_s.singularize}").collect
> do |item|
> +        base_object(model, item.to_s)
>        end
> -      return collection
>      end
>  
>      # Add default attributes [id and href] to class
> -    def base_object(c, model, response)
> -      obj = nil
> -      Nokogiri::XML(response).xpath("#{model.to_s.singularize}").each
> do |item|
> -        c.instance_eval do
> -          attr_accessor :id
> -          attr_accessor :uri
> -
> -
> -        end
> -        obj = xml_to_class(c, item)
> -      end
> -      return obj
> +    def base_object(model, response)
> +      c = DeltaCloud.add_class("#{model}",
> DeltaCloud::guess_model_type(response))
> +      xml_to_class(c,
> Nokogiri::XML(response).xpath("#{model.to_s.singularize}").first)
>      end
>  
>      # Convert XML response to defined Ruby Class
> -    def xml_to_class(c, item)
> -      obj = c.new
> -      # Set default attributes
> -      obj.id = item['id']
> -      api = self
> -      c.instance_eval do
> -        define_method :method_missing do |method|
> -            warn "[WARNING] Method '#{method}' is not available for
> this resource (#{c.name})."
> -            return nil
> -        end
> -        define_method :client do
> -          api
> +    def xml_to_class(base_object, item)
> +      
> +      return nil unless item
> +
> +      params = {
> +          :id => item['id'],
> +          :url => item['href'],
> +          :name => item.name,
> +          :client => self
> +      }
> +      params.merge!({ :initial_state => (item/'state').text.sanitize
> }) if (item/'state').length > 0
> +
> +      obj = base_object.new(params)
> +
> +      # Traverse across XML document and deal with elements
> +      item.xpath('./*').each do |attribute|
> +
> +        # Do a link for elements which are links to other REST
> models
> +        if self.entry_points.keys.include?(:"#{attribute.name}s")
> +          obj.add_link!(attribute.name, attribute['id']) && next
>          end
> -      end
> -      obj.uri = item['href']
> -      logger = @logger
> -      logger << "[DC] Creating class #{obj.class.name}\n"
> -      obj.instance_eval do
> -        # Declare methods for all attributes in object
> -        item.xpath('./*').each do |attribute|
> -          # If attribute is a link to another object then
> -          # create a method which request this object from API
> -          if api.entry_points.keys.include?(:"#{attribute.name}s")
> -            c.instance_eval do
> -              define_method :"#{attribute.name.sanitize}" do
> -                client.send(:"#{attribute.name}", attribute['id'] )
> -              end
> -              logger << "[DC] Added #{attribute.name} to class
> #{obj.class.name}\n"
> -            end
> +
> +        # Do a HWP property for hardware profile properties
> +        if attribute.name == 'property'
> +          if attribute['value'] =~ /^(\d+)$/
> +            obj.add_hwp_property!(attribute['name'], attribute,
> :float) && next
>            else
> -            # Define methods for other attributes
> -            c.instance_eval do
> -              case attribute.name
> -                # When response cointains 'link' block, declare
> -                # methods to call links inside. This is used for
> instance
> -                # to dynamicaly create .stop!, .start! methods
> -                when "actions" then
> -                  actions = []
> -                  attribute.xpath('link').each do |link|
> -                    actions << [link['rel'], link[:href]]
> -                    define_method :"#{link['rel'].sanitize}!" do
> -                      client.request(:"#{link['method']}",
> link['href'], {}, {})
> -                      @current_state = client.send(:"#{item.name}",
> item['id']).state
> -                      obj.instance_eval do |o|
> -                        def state
> -                          @current_state
> -                        end
> -                      end
> -                    end
> -                  end
> -                  define_method :actions do
> -                    actions.collect { |a| a.first }
> -                  end
> -                  define_method :actions_urls do
> -                    urls = {}
> -                    actions.each { |a| urls[a.first] = a.last }
> -                    urls
> -                  end
> -                # Property attribute is handled differently
> -                when "property" then
> -                  attr_accessor :"#{attribute['name'].sanitize}"
> -                  if attribute['value'] =~ /^(\d+)$/
> -                    obj.send(:"#{attribute['name'].sanitize}=",
> -                      DeltaCloud::HWP::FloatProperty.new(attribute,
> attribute['name']))
> -                  else
> -                    obj.send(:"#{attribute['name'].sanitize}=",
> -                      DeltaCloud::HWP::Property.new(attribute,
> attribute['name']))
> -                  end
> -                # Public and private addresses are returned as Array
> -                when "public_addresses", "private_addresses" then
> -                  attr_accessor :"#{attribute.name.sanitize}"
> -                  obj.send(:"#{attribute.name.sanitize}=",
> -                    attribute.xpath('address').collect { |address|
> address.text })
> -                # Value for other attributes are just returned using
> -                # method with same name as attribute (eg. .owner_id,
> .state)
> -                else
> -                  attr_accessor :"#{attribute.name.sanitize}"
> -                  obj.send(:"#{attribute.name.sanitize}=",
> attribute.text.convert)
> -                  logger << "[DC] Added method
> #{attribute.name}[#{attribute.text}] to #{obj.class.name}\n"
> -              end
> -            end
> +            obj.add_hwp_property!(attribute['name'], attribute,
> :integer) && next
>            end
>          end
> +
> +        # If there are actions, add they to
> ActionObject/StateFullObject
> +        if attribute.name == 'actions'
> +          (attribute/'link').each do |link|
> +            obj.add_action_link!(item['id'], link)
> +          end && next
> +        end
> +
> +        # Deal with collections like public-addresses,
> private-addresses
> +        if (attribute/'./*').length > 0
> +          obj.add_collection!(attribute.name, (attribute/'*').collect
> { |value| value.text }) && next
> +        end
> +        
> +        # Anything else is treaten as text object
> +        obj.add_text!(attribute.name, attribute.text.convert)
>        end
> +
>        return obj
>      end
>  
> @@ -252,73 +184,54 @@ module DeltaCloud
>          api_xml = Nokogiri::XML(response)
>          @driver_name = api_xml.xpath('/api').first['driver']
>          @api_version = api_xml.xpath('/api').first['version']
> -        logger << "[API] Version #{@api_version}\n"
> -        logger << "[API] Driver #{@driver_name}\n"
> +        
>          api_xml.css("api > link").each do |entry_point|
>            rel, href = entry_point['rel'].to_sym, entry_point['href']
>            @entry_points.store(rel, href)
> -          logger << "[API] Entry point '#{rel}' added\n"
> +          
>            entry_point.css("feature").each do |feature|
>              @features[rel] ||= []
>              @features[rel] << feature['name'].to_sym
> -            logger << "[API] Feature #{feature['name']} added to
> #{rel}\n"
> +            
>            end
>          end
>        end
>        declare_entry_points_methods(@entry_points)
>      end
>  
> -    def create_key(opts={}, &block)
> -      params = { :name => opts[:name] }
> -      key = nil
> -      request(:post, entry_points[:keys], {}, params) do |response|
> -        c = DeltaCloud.define_class("Key")
> -        key = base_object(c, :key, response)
> -        yield key if block_given?
> -      end
> -      return key
> -    end
> -
> -    # Create a new instance, using image +image_id+. Possible optiosn
> are
> +    # Generate create_* methods dynamically
>      #
> -    #   name  - a user-defined name for the instance
> -    #   realm - a specific realm for placement of the instance
> -    #   hardware_profile - either a string giving the name of the
> -    #                      hardware profile or a hash. The hash must
> have an
> -    #                      entry +id+, giving the id of the hardware
> profile,
> -    #                      and may contain additional names of
> properties,
> -    #                      e.g. 'storage', to override entries in
> the
> -    #                      hardware profile
> -    def create_instance(image_id, opts={}, &block)
> -      name = opts[:name]
> -      realm_id = opts[:realm]
> -      user_data = opts[:user_data]
> -      key_name = opts[:key_name]
> -
> -      params = {}
> -      ( params[:realm_id] = realm_id ) if realm_id
> -      ( params[:name] = name ) if name
> -      ( params[:user_data] = user_data ) if user_data
> -      ( params[:keyname] = key_name ) if key_name
> -
> -      if opts[:hardware_profile].is_a?(String)
> -        params[:hwp_id] = opts[:hardware_profile]
> -      elsif opts[:hardware_profile].is_a?(Hash)
> -        opts[:hardware_profile].each do |k,v|
> -          params[:"hwp_#{k}"] = v
> +    def method_missing(name, *args)
> +      if name.to_s =~ /create_(\w+)/
> +        params = args[0] if args[0] and args[0].class.eql?(Hash)
> +        params ||= args[1] if args[1] and args[1].class.eql?(Hash)
> +        params ||= {}
> +
> +        # FIXME: This fixes are related to Instance model and should
> be
> +        # replaced by 'native' parameter names
> +
> +        params[:realm_id] ||= params[:realm] if params[:realm]
> +        params[:keyname] ||= params[:key_name] if params[:key_name]
> +
> +        if params[:hardware_profile] and
> params[:hardware_profile].class.eql?(Hash)
> +          params[:hardware_profile].each do |k,v|
> +            params[:"hwp_#{k}"] ||= v
> +          end
> +        else
> +          params[:hwp_id] ||= params[:hardware_profile]
>          end
> -      end
>  
> -      params[:image_id] = image_id
> -      instance = nil
> +        params[:image_id] ||= params[:image_id] || args[0] if
> args[0].class!=Hash
>  
> -      request(:post, entry_points[:instances], {}, params) do
> |response|
> -        c = DeltaCloud.define_class("Instance")
> -        instance = base_object(c, :instance, response)
> -        yield instance if block_given?
> -      end
> +        obj = nil
>  
> -      return instance
> +        request(:post, entry_points[:"#{$1}s"], {}, params) do
> |response|
> +          obj = base_object(:"#{$1}", response)
> +          yield obj if block_given?
> +        end
> +        return obj
> +      end
> +      raise NoMethodError
>      end
>  
>      # Basic request method
> @@ -333,9 +246,10 @@ module DeltaCloud
>        if conf[:query_args] != {}
>          conf[:path] += '?' + URI.escape(conf[:query_args].collect{
> |key, value| "#{key}=#{value}" }.join('&')).to_s
>        end
> -      logger << "[#{conf[:method].to_s.upcase}] #{conf[:path]}\n"
> +      
>        if conf[:method].eql?(:post)
>          RestClient.send(:post, conf[:path], conf[:form_data],
> default_headers) do |response, request, block|
> +          handle_backend_error(response) if response.code.eql?(500)
>            if response.respond_to?('body')
>              yield response.body if block_given?
>            else
> @@ -344,6 +258,7 @@ module DeltaCloud
>          end
>        else
>          RestClient.send(conf[:method], conf[:path], default_headers)
> do |response, request, block|
> +          handle_backend_error(response) if response.code.eql?(500)
>            if conf[:method].eql?(:get) and [301, 302, 307].include?
> response.code
>              response.follow_redirection(request) do |response,
> request, block|
>                if response.respond_to?('body')
> @@ -363,6 +278,21 @@ module DeltaCloud
>        end
>      end
>  
> +    # Re-raise backend errors as on exception in client with message
> from
> +    # backend
> +    class BackendError < Exception
> +      def initialize(opts={})
> +        @message = opts[:message]
> +      end
> +      def message
> +        @message
> +      end
> +    end
> +
> +    def handle_backend_error(response)
> +      raise BackendError.new(:message =>
> (Nokogiri::XML(response)/'error/message').text)
> +    end
> +
>      # Check if specified collection have wanted feature
>      def feature?(collection, name)
>        @features.has_key?(collection) &&
> @features[collection].include?(name)
> @@ -396,6 +326,7 @@ module DeltaCloud
>        true if @entry_points!={}
>      end
>  
> +    # This method will retrieve API documentation for given
> collection
>      def documentation(collection, operation=nil)
>        data = {}
>        request(:get, "/docs/#{collection}") do |body|
> @@ -436,151 +367,4 @@ module DeltaCloud
>  
>    end
>  
> -  class Documentation
> -
> -    attr_reader :api, :description, :params, :collection_operations
> -    attr_reader :collection, :operation
> -
> -    def initialize(api, opts={})
> -      @description, @api = opts[:description], api
> -      @params = parse_parameters(opts[:params]) if opts[:params]
> -      @collection_operations = opts[:operations] if
> opts[:operations]
> -      @collection = opts[:collection]
> -      @operation = opts[:operation]
> -      self
> -    end
> -
> -    def operations
> -      @collection_operations.collect { |o|
> api.documentation(@collection, o) }
> -    end
> -
> -    class OperationParameter
> -      attr_reader :name
> -      attr_reader :type
> -      attr_reader :required
> -      attr_reader :description
> -
> -      def initialize(data)
> -        @name, @type, @required, @description = data[:name],
> data[:type], data[:required], data[:description]
> -      end
> -
> -      def to_comment
> -        "   # @param [#{@type}, #{@name}] #{@description}"
> -      end
> -
> -    end
> -
> -    private
> -
> -    def parse_parameters(params)
> -      params.collect { |p| OperationParameter.new(p) }
> -    end
> -
> -  end
> -
> -  module InstanceState
> -
> -    class State
> -      attr_reader :name
> -      attr_reader :transitions
> -
> -      def initialize(name)
> -        @name, @transitions = name, []
> -      end
> -    end
> -
> -    class Transition
> -      attr_reader :to
> -      attr_reader :action
> -
> -      def initialize(to, action)
> -        @to = to
> -        @action = action
> -      end
> -
> -      def auto?
> -        @action.nil?
> -      end
> -    end
> -  end
> -
> -  module HWP
> -
> -   class Property
> -      attr_reader :name, :unit, :value, :kind
> -
> -      def initialize(xml, name)
> -        @name, @kind, @value, @unit = xml['name'],
> xml['kind'].to_sym, xml['value'], xml['unit']
> -        declare_ranges(xml)
> -        self
> -      end
> -
> -      def present?
> -        ! @value.nil?
> -      end
> -
> -      private
> -
> -      def declare_ranges(xml)
> -        case xml['kind']
> -          when 'range' then
> -            self.class.instance_eval do
> -              attr_reader :range
> -            end
> -            @range = { :from => xml.xpath('range').first['first'],
> :to => xml.xpath('range').first['last'] }
> -          when 'enum' then
> -            self.class.instance_eval do
> -              attr_reader :options
> -            end
> -            @options = xml.xpath('enum/entry').collect { |e|
> e['value'] }
> -        end
> -      end
> -
> -    end
> -
> -    # FloatProperty is like Property but return value is Float
> instead of String.
> -    class FloatProperty < Property
> -      def initialize(xml, name)
> -        super(xml, name)
> -        @value = @value.to_f if @value
> -      end
> -    end
> -  end
> -
> -end
> -
> -class String
> -
> -  unless method_defined?(:classify)
> -    # Create a class name from string
> -    def classify
> -      self.singularize.camelize
> -    end
> -  end
> -
> -  unless method_defined?(:camelize)
> -    # Camelize converts strings to UpperCamelCase
> -    def camelize
> -      self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}"
> }.gsub(/(?:^|_)(.)/) { $1.upcase }
> -    end
> -  end
> -
> -  unless method_defined?(:singularize)
> -    # Strip 's' character from end of string
> -    def singularize
> -      self.gsub(/s$/, '')
> -    end
> -  end
> -
> -  # Convert string to float if string value seems like Float
> -  def convert
> -    return self.to_f if self.strip =~ /^([\d\.]+$)/
> -    self
> -  end
> -
> -  # Simply converts whitespaces and - symbols to '_' which is safe
> for Ruby
> -  def sanitize
> -    self.gsub(/(\W+)/, '_')
> -  end
> -
>  end
> diff --git a/client/lib/documentation.rb
> b/client/lib/documentation.rb
> index 6fb5779..c6255af 100644
> --- a/client/lib/documentation.rb
> +++ b/client/lib/documentation.rb
> @@ -1,98 +1,62 @@
> -require 'lib/deltacloud'
> +#
> +# 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.
> +
> +module DeltaCloud
> +  class Documentation
> +
> +    attr_reader :api, :description, :params, :collection_operations
> +    attr_reader :collection, :operation
> +
> +    def initialize(api, opts={})
> +      @description, @api = opts[:description], api
> +      @params = parse_parameters(opts[:params]) if opts[:params]
> +      @collection_operations = opts[:operations] if
> opts[:operations]
> +      @collection = opts[:collection]
> +      @operation = opts[:operation]
> +      self
> +    end
>  
> -skip_methods = [ "id=", "uri=" ]
> +    def operations
> +      @collection_operations.collect { |o|
> api.documentation(@collection, o) }
> +    end
>  
> -begin
> -  @dc=DeltaCloud.new('mockuser', 'mockpassword',
> 'http://localhost:3001/api')
> -rescue
> -  puts "Please make sure that Deltacloud API is running with Mock
> driver"
> -  exit(1)
> -end
> +    class OperationParameter
> +      attr_reader :name
> +      attr_reader :type
> +      attr_reader :required
> +      attr_reader :description
>  
> -@dc.entry_points.keys.each do |ep|
> -  @dc.send(ep)
> -end
> +      def initialize(data)
> +        @name, @type, @required, @description = data[:name],
> data[:type], data[:required], data[:description]
> +      end
>  
> -class_list = DeltaCloud::classes.collect { |c|
> DeltaCloud::module_eval("::DeltaCloud::API::#{c}")}
> +      def to_comment
> +        "   # @param [#{@type}, #{@name}] #{@description}"
> +      end
>  
> -def read_method_description(c, method)
> -  if method =~ /es$/
> -    "    # Read #{c.downcase} collection from Deltacloud API"
> -  else
> -    case method
> -      when "uri" then
> -        "    # Return URI to API for this object"
> -      when "action_urls" then
> -        "    # Return available actions API URL"
> -      when "client" then
> -        "    # Return instance of API client"
> -      else
> -        "    # Get #{method} attribute value from #{c.downcase}"
>      end
> -  end
> -end
>  
> -def read_parameters(c, method)
> -  out = []
> -  if method =~ /es$/
> -    out << "    # @param [String, #id] Filter by ID"
> -  end
> -  out.join("\n")
> -end
> +    private
>  
> -def read_return_value(c, method)
> -  if method =~ /es$/
> -    rt = "Array"
> -  else
> -    rt = "String"
> -  end
> -  "    # @return [String] Value of #{method}"
> -end
> -
> -out = []
> -
> -class_list.each do |c|
> -  class_name = "#{c}".gsub(/^DeltaCloud::/, '')
> -  out << "module DeltaCloud"
> -  out << "  class API"
> -  @dc.entry_points.keys.each do |ep|
> -    out << "# Return #{ep.to_s.classify} object with given id\n"
> -    out << "# "
> -    out << "#
> #{@dc.documentation(ep.to_s).description.split("\n").join("\n# ")}"
> -    out << "# @return [#{ep.to_s.classify}]"
> -    out << "def #{ep.to_s.gsub(/s$/, '')}"
> -    out << "end"
> -    out << "# Return collection of #{ep.to_s.classify} objects"
> -    out << "# "
> -    out << "#
> #{@dc.documentation(ep.to_s).description.split("\n").join("\n# ")}"
> -    @dc.documentation(ep.to_s, 'index').params.each do |p|
> -      out << p.to_comment
> +    def parse_parameters(params)
> +      params.collect { |p| OperationParameter.new(p) }
>      end
> -    out << "# @return [Array] [#{ep.to_s.classify}]"
> -    out << "def #{ep}(opts={})"
> -    out << "end"
> -  end
> -  out << "  end"
> -  out << "  class #{class_name}"
> -  c.instance_methods(false).each do |method|
> -    next if skip_methods.include?(method)
> -    params = read_parameters(class_name, method)
> -    retval = read_return_value(class_name, method)
> -    out << read_method_description(class_name, method)
> -    out << params if params
> -    out << retval if retval
> -    out << "    def #{method}"
> -    out << "      # This method was generated dynamically from API"
> -    out << "    end\n"
> +
>    end
> -  out << "  end"
> -  out << "end"
> -end
>  
> -FileUtils.rm_r('doc') rescue nil
> -FileUtils.mkdir_p('doc')
> -File.open('doc/deltacloud.rb', 'w') do |f|
> -  f.puts(out.join("\n"))
>  end
> -system("yardoc -m markdown --readme README --title 'Deltacloud Client
> Library' 'lib/*.rb' 'doc/deltacloud.rb' --verbose")
> -FileUtils.rm('doc/deltacloud.rb')
> diff --git a/client/lib/hwp_properties.rb
> b/client/lib/hwp_properties.rb
> new file mode 100644
> index 0000000..86ccf00
> --- /dev/null
> +++ b/client/lib/hwp_properties.rb
> @@ -0,0 +1,64 @@
> +#
> +# Copyright (C) 2009  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.
> +
> +module DeltaCloud
> +
> +  module HWP
> +
> +   class Property
> +      attr_reader :name, :unit, :value, :kind
> +
> +      def initialize(xml, name)
> +        @name, @kind, @value, @unit = xml['name'],
> xml['kind'].to_sym, xml['value'], xml['unit']
> +        declare_ranges(xml)
> +        self
> +      end
> +
> +      def present?
> +        ! @value.nil?
> +      end
> +
> +      private
> +
> +      def declare_ranges(xml)
> +        case xml['kind']
> +          when 'range' then
> +            self.class.instance_eval do
> +              attr_reader :range
> +            end
> +            @range = { :from => xml.xpath('range').first['first'],
> :to => xml.xpath('range').first['last'] }
> +          when 'enum' then
> +            self.class.instance_eval do
> +              attr_reader :options
> +            end
> +            @options = xml.xpath('enum/entry').collect { |e|
> e['value'] }
> +        end
> +      end
> +
> +    end
> +
> +    # FloatProperty is like Property but return value is Float
> instead of String.
> +    class FloatProperty < Property
> +      def initialize(xml, name)
> +        super(xml, name)
> +        @value = @value.to_f if @value
> +      end
> +    end
> +  end
> +
> +end
> diff --git a/client/lib/instance_state.rb
> b/client/lib/instance_state.rb
> new file mode 100644
> index 0000000..ebc59c4
> --- /dev/null
> +++ b/client/lib/instance_state.rb
> @@ -0,0 +1,29 @@
> +module DeltaCloud
> +  module InstanceState
> +
> +    class State
> +      attr_reader :name
> +      attr_reader :transitions
> +
> +      def initialize(name)
> +        @name, @transitions = name, []
> +      end
> +    end
> +
> +    class Transition
> +      attr_reader :to
> +      attr_reader :action
> +
> +      def initialize(to, action)
> +        @to = to
> +        @action = action
> +      end
> +
> +      def auto?
> +        @action.nil?
> +      end
> +    end
> +
> +  end
> +
> +end
> diff --git a/client/lib/string.rb b/client/lib/string.rb
> new file mode 100644
> index 0000000..72cc259
> --- /dev/null
> +++ b/client/lib/string.rb
> @@ -0,0 +1,53 @@
> +#
> +# 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.
> +
> +class String
> +
> +  unless method_defined?(:classify)
> +    # Create a class name from string
> +    def classify
> +      self.singularize.camelize
> +    end
> +  end
> +
> +  unless method_defined?(:camelize)
> +    # Camelize converts strings to UpperCamelCase
> +    def camelize
> +      self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}"
> }.gsub(/(?:^|_)(.)/) { $1.upcase }
> +    end
> +  end
> +
> +  unless method_defined?(:singularize)
> +    # Strip 's' character from end of string
> +    def singularize
> +      self.gsub(/s$/, '')
> +    end
> +  end
> +
> +  # Convert string to float if string value seems like Float
> +  def convert
> +    return self.to_f if self.strip =~ /^([\d\.]+$)/
> +    self
> +  end
> +
> +  # Simply converts whitespaces and - symbols to '_' which is safe
> for Ruby
> +  def sanitize
> +    self.strip.gsub(/(\W+)/, '_')
> +  end
> +
> +end
> diff --git a/client/specs/hardware_profiles_spec.rb
> b/client/specs/hardware_profiles_spec.rb
> index d11eb36..7957e99 100644
> --- a/client/specs/hardware_profiles_spec.rb
> +++ b/client/specs/hardware_profiles_spec.rb
> @@ -64,7 +64,7 @@ describe "hardware_profiles" do
>    it "should allow fetching different hardware_profiles" do
>      client = DeltaCloud.new( API_NAME, API_PASSWORD, API_URL )
>      hwp1 = client.hardware_profile( 'm1-small' )
> -    hwp2 = client.hardware_profile( 'm1-xlarge' )
> +    hwp2 = client.hardware_profile( 'm1-large' )
>      hwp1.storage.value.should_not eql(hwp2.storage.value)
>      hwp1.memory.value.should_not eql(hwp2.memory.value)
>    end
> diff --git a/client/specs/instances_spec.rb
> b/client/specs/instances_spec.rb
> index c4995ae..bfe29bb 100644
> --- a/client/specs/instances_spec.rb
> +++ b/client/specs/instances_spec.rb
> @@ -85,7 +85,7 @@ describe "instances" do
>  
>    it "should allow creation of new instances with reasonable
> defaults" do
>      DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client|
> -      instance = client.create_instance( 'img1',
> :name=>'TestInstance' )
> +      instance = client.create_instance( 'img1',
> :name=>'TestInstance', :hardware_profile => 'm1-large' )
>        instance.should_not be_nil
>        instance.uri.should match( %r{#{API_URL}/instances/inst[0-9]+}
> )
>        instance.id.should match( /inst[0-9]+/ )
> @@ -98,7 +98,7 @@ describe "instances" do
>  
>    it "should allow creation of new instances with specific realm" do
>      DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client|
> -      instance = client.create_instance( 'img1', :realm=>'eu' )
> +      instance = client.create_instance( 'img1', :realm=>'eu',
> :hardware_profile => 'm1-large' )
>        instance.should_not be_nil
>        instance.uri.should match( %r{#{API_URL}/instances/inst[0-9]+}
> )
>        instance.id.should match( /inst[0-9]+/ )
> -- 
> 1.7.2.3

I still have to test it locally after that I will send ACK if nobody else does first.

-- Ladislav

Mime
View raw message