You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@deltacloud.apache.org by mf...@redhat.com on 2010/10/15 17:42:35 UTC
[PATCH core 2/4] Fixed xml_to_class method in client
From: Michal Fojtik <mf...@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
+
+ private
+
+ def search_for_method(name)
+ @objects.select { |o| o[:method_name] == "#{name}" }.first
+ 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.select {|o| o[:type].eql?(:action_link) }.collect { |o| [o[:rel], o[:href]] }
+ 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)
+ 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