steamcannon-deltacloud-client 0.0.9.8.1 → 0.1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -25,15 +25,9 @@ task 'documentation' do
25
25
  load 'lib/documentation.rb'
26
26
  end
27
27
 
28
- @specs = ['ruby', 'java'].inject({}) do |hash, spec_platform|
29
- $platform = spec_platform
30
- hash.update(spec_platform => Gem::Specification.load('deltacloud-client.gemspec'))
31
- end
32
-
33
- @specs.values.each do |spec|
34
- Rake::GemPackageTask.new(spec) do |pkg|
35
- pkg.need_tar = true
36
- end
28
+ spec = Gem::Specification.load('deltacloud-client.gemspec')
29
+ Rake::GemPackageTask.new(spec) do |pkg|
30
+ pkg.need_tar = true
37
31
  end
38
32
 
39
33
  if Gem.available?('rspec')
data/bin/deltacloudc CHANGED
@@ -124,6 +124,7 @@ if options[:collection] and options[:operation]
124
124
  # If collection is set and requested operation is 'show' just 'singularize'
125
125
  # collection name and print item with specified id (-i parameter)
126
126
  if options[:operation].eql?('show')
127
+ invalid_usage("Missing ID, must be provided with --id") unless options[:id]
127
128
  puts format(client.send(options[:collection].gsub(/s$/, ''), options[:id]))
128
129
  exit(0)
129
130
  end
@@ -0,0 +1,287 @@
1
+ #
2
+ # Copyright (C) 2010 Red Hat, Inc.
3
+ #
4
+ # Licensed to the Apache Software Foundation (ASF) under one or more
5
+ # contributor license agreements. See the NOTICE file distributed with
6
+ # this work for additional information regarding copyright ownership. The
7
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance with the
9
+ # License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
+ # License for the specific language governing permissions and limitations
17
+ # under the License.
18
+
19
+ require 'string'
20
+
21
+ module DeltaCloud
22
+
23
+ class BaseObjectParamError < Exception; end
24
+ class NoHandlerForMethod < Exception; end
25
+
26
+ # BaseObject model basically provide the basic operation around
27
+ # REST model, like defining a links between different objects,
28
+ # element with text values, or collection of these elements
29
+ class BaseObject
30
+ attr_reader :id, :url, :client, :base_name
31
+ attr_reader :objects
32
+
33
+ alias :uri :url
34
+
35
+ # For initializing new object you require to set
36
+ # id, url, client and name attribute.
37
+ def initialize(opts={}, &block)
38
+ @id, @url, @client, @base_name = opts[:id], opts[:url], opts[:client], opts[:name]
39
+ @objects = []
40
+ raise BaseObjectParamError if @id.nil? or @url.nil? or @client.nil? or @base_name.nil?
41
+ yield self if block_given?
42
+ end
43
+
44
+ # This method add link to another object in REST model
45
+ # XML syntax: <link rel="destroy" href="http://localhost/api/resource" method="post"/>
46
+ def add_link!(object_name, id)
47
+ @objects << {
48
+ :type => :link,
49
+ :method_name => object_name.sanitize,
50
+ :id => id
51
+ }
52
+ @objects << {
53
+ :type => :text,
54
+ :method_name => "#{object_name.sanitize}_id",
55
+ :value => id
56
+ }
57
+ end
58
+
59
+ # Method add property for hardware profile
60
+ def add_hwp_property!(name, property, type)
61
+ hwp_property=case type
62
+ when :float then DeltaCloud::HWP::FloatProperty.new(property, name)
63
+ when :integer then DeltaCloud::HWP::Property.new(property, name)
64
+ end
65
+ @objects << {
66
+ :type => :property,
67
+ :method_name => name.sanitize,
68
+ :property => hwp_property
69
+ }
70
+ end
71
+
72
+ # This method define text object in REST model
73
+ # XML syntax: <name>Instance 1</name>
74
+ def add_text!(object_name, value)
75
+ @objects << {
76
+ :type => :text,
77
+ :method_name => object_name.sanitize,
78
+ :value => value
79
+ }
80
+ end
81
+
82
+ # This method define collection of text elements inside REST model
83
+ # XML syntax: <addresses>
84
+ # <address>127.0.0.1</address>
85
+ # <address>127.0.0.2</address>
86
+ # </addresses>
87
+ def add_collection!(collection_name, values=[])
88
+ @objects << {
89
+ :type => :collection,
90
+ :method_name => collection_name.sanitize,
91
+ :values => values
92
+ }
93
+ end
94
+
95
+ # Basic method hander. This define a way how value from property
96
+ # will be returned
97
+ def method_handler(m, args=[])
98
+ case m[:type]
99
+ when :link then return @client.send(m[:method_name].singularize, m[:id])
100
+ when :text then return m[:value]
101
+ when :property then return m[:property]
102
+ when :collection then return m[:values]
103
+ else raise NoHandlerForMethod
104
+ end
105
+ end
106
+
107
+ def method_missing(method_name, *args)
108
+ # First of all search throught array for method name
109
+ m = search_for_method(method_name)
110
+ if m.nil?
111
+ warn "[WARNING] Method unsupported by API: '#{self.class}.#{method_name}(#{args.inspect})'"
112
+ return nil
113
+ else
114
+ # Call appropriate handler for method
115
+ method_handler(m, args)
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def search_for_method(name)
122
+ @objects.detect { |o| o[:method_name] == "#{name}" }
123
+ end
124
+
125
+ end
126
+
127
+ class ActionObject < BaseObject
128
+
129
+ def initialize(opts={}, &block)
130
+ super(opts)
131
+ @action_urls = opts[:action_urls] || []
132
+ @actions = []
133
+ end
134
+
135
+ # This trigger is called right after action.
136
+ # This method does nothing inside ActionObject
137
+ # but it can be redifined and used in meta-programming
138
+ def action_trigger(action)
139
+ end
140
+
141
+ def add_action_link!(id, link)
142
+ m = {
143
+ :type => :action_link,
144
+ :method_name => "#{link['rel'].sanitize}!",
145
+ :id => id,
146
+ :href => link['href'],
147
+ :rel => link['rel'].sanitize,
148
+ :method => link['method'].sanitize
149
+ }
150
+ @objects << m
151
+ @actions << [m[:rel], m[:href]]
152
+ @action_urls << m[:href]
153
+ end
154
+
155
+ def actions
156
+ @objects.inject([]) do |result, item|
157
+ result << [item[:rel], item[:href]] if item[:type].eql?(:action_link)
158
+ result
159
+ end
160
+ end
161
+
162
+ def action_urls
163
+ actions.collect { |a| a.last }
164
+ end
165
+
166
+ alias :base_method_handler :method_handler
167
+
168
+ # First call BaseObject method handler,
169
+ # then, if not method found try ActionObject handler
170
+ def method_handler(m, args=[])
171
+ begin
172
+ base_method_handler(m, args)
173
+ rescue NoHandlerForMethod
174
+ case m[:type]
175
+ when :action_link then do_action(m, args)
176
+ else raise NoHandlerForMethod
177
+ end
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ def do_action(m, args)
184
+ args = args.first || {}
185
+ method = m[:method].to_sym
186
+ @client.request(method,
187
+ m[:href],
188
+ method == :get ? args : {},
189
+ method == :get ? {} : args)
190
+ action_trigger(m[:rel])
191
+ end
192
+
193
+ end
194
+
195
+ class StateFullObject < ActionObject
196
+ attr_reader :state
197
+
198
+ def initialize(opts={}, &block)
199
+ super(opts)
200
+ @state = opts[:initial_state] || ''
201
+ add_default_states!
202
+ end
203
+
204
+ def add_default_states!
205
+ @objects << {
206
+ :method_name => 'stopped?',
207
+ :type => :state,
208
+ :state => 'STOPPED'
209
+ }
210
+ @objects << {
211
+ :method_name => 'running?',
212
+ :type => :state,
213
+ :state => 'RUNNING'
214
+ }
215
+ @objects << {
216
+ :method_name => 'pending?',
217
+ :type => :state,
218
+ :state => 'PENDING'
219
+ }
220
+ @objects << {
221
+ :method_name => 'shutting_down?',
222
+ :type => :state,
223
+ :state => 'SHUTTING_DOWN'
224
+ }
225
+ end
226
+
227
+ def action_trigger(action)
228
+ # Refresh object state after action
229
+ @new_state_object = @client.send(self.base_name, self.id)
230
+ @state = @new_state_object.state
231
+ self.update_actions!
232
+ end
233
+
234
+ alias :action_method_handler :method_handler
235
+
236
+ def method_handler(m, args=[])
237
+ begin
238
+ action_method_handler(m, args)
239
+ rescue NoHandlerForMethod
240
+ case m[:type]
241
+ when :state then evaluate_state(m[:state], @state)
242
+ else raise NoHandlerForMethod
243
+ end
244
+ end
245
+ end
246
+
247
+ # private
248
+
249
+ def evaluate_state(method_state, current_state)
250
+ method_state.eql?(current_state)
251
+ end
252
+
253
+ def action_objects
254
+ @objects.select { |o| o[:type] == :action_link }
255
+ end
256
+
257
+ def update_actions!
258
+ new_actions = @new_state_object.action_objects
259
+ @objects.reject! { |o| o[:type] == :action_link }
260
+ @objects = (@objects + new_actions)
261
+ end
262
+
263
+ end
264
+
265
+ def self.add_class(name, parent=:base)
266
+ parent_class = case parent
267
+ when :base then 'BaseObject'
268
+ when :action then 'ActionObject'
269
+ when :state then 'StateFullObject'
270
+ end
271
+ @defined_classes ||= []
272
+ if @defined_classes.include?(name)
273
+ DeltaCloud::API.class_eval("#{name.classify}")
274
+ else
275
+ DeltaCloud::API.class_eval("class #{name.classify} < DeltaCloud::#{parent_class}; end")
276
+ DeltaCloud::API.const_get("#{name.classify}")
277
+ end
278
+ end
279
+
280
+ def self.guess_model_type(response)
281
+ response = Nokogiri::XML(response.to_s)
282
+ return :action if ((response/'//actions').length == 1) and ((response/'//state').length == 0)
283
+ return :state if ((response/'//actions').length == 1) and ((response/'//state').length == 1)
284
+ return :base
285
+ end
286
+
287
+ end
data/lib/deltacloud.rb CHANGED
@@ -21,6 +21,11 @@ require 'rest_client'
21
21
  require 'base64'
22
22
  require 'logger'
23
23
 
24
+ require 'hwp_properties'
25
+ require 'instance_state'
26
+ require 'documentation'
27
+ require 'base_object'
28
+
24
29
  module DeltaCloud
25
30
 
26
31
  # Get a new API client instance
@@ -56,27 +61,11 @@ module DeltaCloud
56
61
  API.new(nil, nil, url).driver_name
57
62
  end
58
63
 
59
- def self.define_class(name)
60
- @defined_classes ||= []
61
- if @defined_classes.include?(name)
62
- self.module_eval("API::#{name}")
63
- else
64
- @defined_classes << name unless @defined_classes.include?(name)
65
- API.const_set(name, Class.new)
66
- end
67
- end
68
-
69
- def self.classes
70
- @defined_classes || []
71
- end
72
-
73
64
  class API
74
- attr_accessor :logger
75
65
  attr_reader :api_uri, :driver_name, :api_version, :features, :entry_points
76
66
 
77
67
  def initialize(user_name, password, api_url, opts={}, &block)
78
68
  opts[:version] = true
79
- @logger = opts[:verbose] ? Logger.new(STDERR) : []
80
69
  @username, @password = user_name, password
81
70
  @api_uri = URI.parse(api_url)
82
71
  @features, @entry_points = {}, {}
@@ -101,147 +90,97 @@ module DeltaCloud
101
90
  # Define methods based on 'rel' attribute in entry point
102
91
  # Two methods are declared: 'images' and 'image'
103
92
  def declare_entry_points_methods(entry_points)
104
- logger = @logger
93
+
105
94
  API.instance_eval do
106
95
  entry_points.keys.select {|k| [:instance_states].include?(k)==false }.each do |model|
96
+
107
97
  define_method model do |*args|
108
98
  request(:get, entry_points[model], args.first) do |response|
109
- # Define a new class based on model name
110
- c = DeltaCloud.define_class("#{model.to_s.classify}")
111
- # Create collection from index operation
112
- base_object_collection(c, model, response)
99
+ base_object_collection(model, response)
113
100
  end
114
101
  end
115
- logger << "[API] Added method #{model}\n"
102
+
116
103
  define_method :"#{model.to_s.singularize}" do |*args|
117
104
  request(:get, "#{entry_points[model]}/#{args[0]}") do |response|
118
- # Define a new class based on model name
119
- c = DeltaCloud.define_class("#{model.to_s.classify}")
120
- # Build class for returned object
121
- base_object(c, model, response)
105
+ base_object(model, response)
122
106
  end
123
107
  end
124
- logger << "[API] Added method #{model.to_s.singularize}\n"
108
+
125
109
  define_method :"fetch_#{model.to_s.singularize}" do |url|
126
110
  id = url.grep(/\/#{model}\/(.*)$/)
127
111
  self.send(model.to_s.singularize.to_sym, $1)
128
112
  end
113
+
129
114
  end
130
115
  end
131
116
  end
132
117
 
133
- def base_object_collection(c, model, response)
134
- collection = []
135
- Nokogiri::XML(response).xpath("#{model}/#{model.to_s.singularize}").each do |item|
136
- c.instance_eval do
137
- attr_accessor :id
138
- attr_accessor :uri
139
- end
140
- collection << xml_to_class(c, item)
118
+ def base_object_collection(model, response)
119
+ Nokogiri::XML(response).xpath("#{model}/#{model.to_s.singularize}").collect do |item|
120
+ base_object(model, item.to_s)
141
121
  end
142
- return collection
143
122
  end
144
123
 
145
124
  # Add default attributes [id and href] to class
146
- def base_object(c, model, response)
147
- obj = nil
148
- Nokogiri::XML(response).xpath("#{model.to_s.singularize}").each do |item|
149
- c.instance_eval do
150
- attr_accessor :id
151
- attr_accessor :uri
152
-
153
-
154
- end
155
- obj = xml_to_class(c, item)
156
- end
157
- return obj
125
+ def base_object(model, response)
126
+ c = DeltaCloud.add_class("#{model}", DeltaCloud::guess_model_type(response))
127
+ xml_to_class(c, Nokogiri::XML(response).xpath("#{model.to_s.singularize}").first)
158
128
  end
159
129
 
160
130
  # Convert XML response to defined Ruby Class
161
- def xml_to_class(c, item)
162
- obj = c.new
163
- # Set default attributes
164
- obj.id = item['id']
165
- api = self
166
- c.instance_eval do
167
- define_method :method_missing do |method|
168
- warn "[WARNING] Method '#{method}' is not available for this resource (#{c.name})."
169
- return nil
170
- end
171
- define_method :client do
172
- api
131
+ def xml_to_class(base_object, item)
132
+
133
+ return nil unless item
134
+
135
+ params = {
136
+ :id => item['id'],
137
+ :url => item['href'],
138
+ :name => item.name,
139
+ :client => self
140
+ }
141
+ params.merge!({ :initial_state => (item/'state').text.sanitize }) if (item/'state').length > 0
142
+
143
+ obj = base_object.new(params)
144
+
145
+ # Traverse across XML document and deal with elements
146
+ item.xpath('./*').each do |attribute|
147
+
148
+ # Do a link for elements which are links to other REST models
149
+ if self.entry_points.keys.include?(:"#{attribute.name}s")
150
+ obj.add_link!(attribute.name, attribute['id']) && next
173
151
  end
174
- end
175
- obj.uri = item['href']
176
- logger = @logger
177
- logger << "[DC] Creating class #{obj.class.name}\n"
178
- obj.instance_eval do
179
- # Declare methods for all attributes in object
180
- item.xpath('./*').each do |attribute|
181
- # If attribute is a link to another object then
182
- # create a method which request this object from API
183
- if api.entry_points.keys.include?(:"#{attribute.name}s")
184
- c.instance_eval do
185
- define_method :"#{attribute.name.sanitize}" do
186
- client.send(:"#{attribute.name}", attribute['id'] )
187
- end
188
- logger << "[DC] Added #{attribute.name} to class #{obj.class.name}\n"
189
- end
152
+
153
+ # Do a HWP property for hardware profile properties
154
+ if attribute.name == 'property'
155
+ if attribute['value'] =~ /^(\d+)$/
156
+ obj.add_hwp_property!(attribute['name'], attribute, :float) && next
190
157
  else
191
- # Define methods for other attributes
192
- c.instance_eval do
193
- case attribute.name
194
- # When response cointains 'link' block, declare
195
- # methods to call links inside. This is used for instance
196
- # to dynamicaly create .stop!, .start! methods
197
- when "actions":
198
- actions = []
199
- attribute.xpath('link').each do |link|
200
- actions << [link['rel'], link[:href]]
201
- define_method :"#{link['rel'].sanitize}!" do |*params|
202
- client.request(:"#{link['method']}", link['href'], {}, params.first || {})
203
- @current_state = client.send(:"#{item.name}", item['id']).state
204
- obj.instance_eval do |o|
205
- def state
206
- @current_state
207
- end
208
- end
209
- end
210
- end
211
- define_method :actions do
212
- actions.collect { |a| a.first }
213
- end
214
- define_method :actions_urls do
215
- urls = {}
216
- actions.each { |a| urls[a.first] = a.last }
217
- urls
218
- end
219
- # Property attribute is handled differently
220
- when "property":
221
- attr_accessor :"#{attribute['name'].sanitize}"
222
- if attribute['value'] =~ /^(\d+)$/
223
- obj.send(:"#{attribute['name'].sanitize}=",
224
- DeltaCloud::HWP::FloatProperty.new(attribute, attribute['name']))
225
- else
226
- obj.send(:"#{attribute['name'].sanitize}=",
227
- DeltaCloud::HWP::Property.new(attribute, attribute['name']))
228
- end
229
- # Public and private addresses are returned as Array
230
- when "public_addresses", "private_addresses":
231
- attr_accessor :"#{attribute.name.sanitize}"
232
- obj.send(:"#{attribute.name.sanitize}=",
233
- attribute.xpath('address').collect { |address| address.text })
234
- # Value for other attributes are just returned using
235
- # method with same name as attribute (eg. .owner_id, .state)
236
- else
237
- attr_accessor :"#{attribute.name.sanitize}"
238
- obj.send(:"#{attribute.name.sanitize}=", attribute.text.convert)
239
- logger << "[DC] Added method #{attribute.name}[#{attribute.text}] to #{obj.class.name}\n"
240
- end
241
- end
158
+ obj.add_hwp_property!(attribute['name'], attribute, :integer) && next
242
159
  end
243
160
  end
161
+
162
+ # If there are actions, add they to ActionObject/StateFullObject
163
+ if attribute.name == 'actions'
164
+ (attribute/'link').each do |link|
165
+ obj.add_action_link!(item['id'], link)
166
+ end && next
167
+ end
168
+
169
+ if attribute.name == 'mount'
170
+ obj.add_link!("instance", (attribute/"./instance/@id").first.value)
171
+ obj.add_text!("device", (attribute/"./device/@name").first.value)
172
+ next
173
+ end
174
+
175
+ # Deal with collections like public-addresses, private-addresses
176
+ if (attribute/'./*').length > 0
177
+ obj.add_collection!(attribute.name, (attribute/'*').collect { |value| value.text }) && next
178
+ end
179
+
180
+ # Anything else is treaten as text object
181
+ obj.add_text!(attribute.name, attribute.text.convert)
244
182
  end
183
+
245
184
  return obj
246
185
  end
247
186
 
@@ -252,90 +191,54 @@ module DeltaCloud
252
191
  api_xml = Nokogiri::XML(response)
253
192
  @driver_name = api_xml.xpath('/api').first['driver']
254
193
  @api_version = api_xml.xpath('/api').first['version']
255
- logger << "[API] Version #{@api_version}\n"
256
- logger << "[API] Driver #{@driver_name}\n"
194
+
257
195
  api_xml.css("api > link").each do |entry_point|
258
196
  rel, href = entry_point['rel'].to_sym, entry_point['href']
259
197
  @entry_points.store(rel, href)
260
- logger << "[API] Entry point '#{rel}' added\n"
198
+
261
199
  entry_point.css("feature").each do |feature|
262
200
  @features[rel] ||= []
263
201
  @features[rel] << feature['name'].to_sym
264
- logger << "[API] Feature #{feature['name']} added to #{rel}\n"
202
+
265
203
  end
266
204
  end
267
205
  end
268
206
  declare_entry_points_methods(@entry_points)
269
207
  end
270
208
 
271
- def create_key(opts={}, &block)
272
- params = { :name => opts[:name] }
273
- key = nil
274
- request(:post, entry_points[:keys], {}, params) do |response|
275
- c = DeltaCloud.define_class("Key")
276
- key = base_object(c, :key, response)
277
- yield key if block_given?
278
- end
279
- return key
280
- end
281
-
282
- # Create a new instance, using image +image_id+. Possible optiosn are
209
+ # Generate create_* methods dynamically
283
210
  #
284
- # name - a user-defined name for the instance
285
- # realm - a specific realm for placement of the instance
286
- # hardware_profile - either a string giving the name of the
287
- # hardware profile or a hash. The hash must have an
288
- # entry +id+, giving the id of the hardware profile,
289
- # and may contain additional names of properties,
290
- # e.g. 'storage', to override entries in the
291
- # hardware profile
292
- def create_instance(image_id, opts={}, &block)
293
- name = opts[:name]
294
- realm_id = opts[:realm]
295
- user_data = opts[:user_data]
296
- key_name = opts[:key_name]
297
- security_group = opts[:security_group]
298
-
299
- params = {}
300
- ( params[:realm_id] = realm_id ) if realm_id
301
- ( params[:name] = name ) if name
302
- ( params[:user_data] = user_data ) if user_data
303
- ( params[:keyname] = key_name ) if key_name
304
- ( params[:security_group] = security_group) if security_group
305
-
306
- if opts[:hardware_profile].is_a?(String)
307
- params[:hwp_id] = opts[:hardware_profile]
308
- elsif opts[:hardware_profile].is_a?(Hash)
309
- opts[:hardware_profile].each do |k,v|
310
- params[:"hwp_#{k}"] = v
311
- end
312
- end
211
+ def method_missing(name, *args)
212
+ if name.to_s =~ /create_(\w+)/
213
+ params = args[0] if args[0] and args[0].class.eql?(Hash)
214
+ params ||= args[1] if args[1] and args[1].class.eql?(Hash)
215
+ params ||= {}
313
216
 
314
- params[:image_id] = image_id
315
- instance = nil
217
+ # FIXME: This fixes are related to Instance model and should be
218
+ # replaced by 'native' parameter names
316
219
 
317
- request(:post, entry_points[:instances], {}, params) do |response|
318
- c = DeltaCloud.define_class("Instance")
319
- instance = base_object(c, :instance, response)
320
- yield instance if block_given?
321
- end
220
+ params[:realm_id] ||= params[:realm] if params[:realm]
221
+ params[:keyname] ||= params[:key_name] if params[:key_name]
322
222
 
323
- return instance
324
- end
223
+ if params[:hardware_profile] and params[:hardware_profile].class.eql?(Hash)
224
+ params[:hardware_profile].each do |k,v|
225
+ params[:"hwp_#{k}"] ||= v
226
+ end
227
+ else
228
+ params[:hwp_id] ||= params[:hardware_profile]
229
+ end
325
230
 
231
+ params[:image_id] ||= params[:image_id] || args[0] if args[0].class!=Hash
326
232
 
327
- def create_storage_volume(opts={}, &block)
328
- params = opts.dup
329
- params[:realm_id] ||= params.delete(:realm)
330
- storage_volume = nil
331
-
332
- request(:post, entry_points[:storage_volumes], {}, params) do |response|
333
- c = DeltaCloud.define_class("StorageVolume")
334
- storage_volume = base_object(c, :storage_volume, response)
335
- yield storage_volume if block_given?
336
- end
233
+ obj = nil
337
234
 
338
- storage_volume
235
+ request(:post, entry_points[:"#{$1}s"], {}, params) do |response|
236
+ obj = base_object(:"#{$1}", response)
237
+ yield obj if block_given?
238
+ end
239
+ return obj
240
+ end
241
+ raise NoMethodError
339
242
  end
340
243
 
341
244
  # Basic request method
@@ -350,9 +253,10 @@ module DeltaCloud
350
253
  if conf[:query_args] != {}
351
254
  conf[:path] += '?' + URI.escape(conf[:query_args].collect{ |key, value| "#{key}=#{value}" }.join('&')).to_s
352
255
  end
353
- logger << "[#{conf[:method].to_s.upcase}] #{conf[:path]}\n"
256
+
354
257
  if conf[:method].eql?(:post)
355
258
  RestClient.send(:post, conf[:path], conf[:form_data], default_headers) do |response, request, block|
259
+ handle_backend_error(response) if response.code.eql?(500)
356
260
  if response.respond_to?('body')
357
261
  yield response.body if block_given?
358
262
  else
@@ -361,6 +265,7 @@ module DeltaCloud
361
265
  end
362
266
  else
363
267
  RestClient.send(conf[:method], conf[:path], default_headers) do |response, request, block|
268
+ handle_backend_error(response) if response.code.eql?(500)
364
269
  if conf[:method].eql?(:get) and [301, 302, 307].include? response.code
365
270
  response.follow_redirection(request) do |response, request, block|
366
271
  if response.respond_to?('body')
@@ -380,6 +285,21 @@ module DeltaCloud
380
285
  end
381
286
  end
382
287
 
288
+ # Re-raise backend errors as on exception in client with message from
289
+ # backend
290
+ class BackendError < Exception
291
+ def initialize(opts={})
292
+ @message = opts[:message]
293
+ end
294
+ def message
295
+ @message
296
+ end
297
+ end
298
+
299
+ def handle_backend_error(response)
300
+ raise BackendError.new(:message => (Nokogiri::XML(response)/'error/message').text)
301
+ end
302
+
383
303
  # Check if specified collection have wanted feature
384
304
  def feature?(collection, name)
385
305
  @features.has_key?(collection) && @features[collection].include?(name)
@@ -413,6 +333,7 @@ module DeltaCloud
413
333
  true if @entry_points!={}
414
334
  end
415
335
 
336
+ # This method will retrieve API documentation for given collection
416
337
  def documentation(collection, operation=nil)
417
338
  data = {}
418
339
  request(:get, "/docs/#{collection}") do |body|
@@ -453,151 +374,4 @@ module DeltaCloud
453
374
 
454
375
  end
455
376
 
456
- class Documentation
457
-
458
- attr_reader :api, :description, :params, :collection_operations
459
- attr_reader :collection, :operation
460
-
461
- def initialize(api, opts={})
462
- @description, @api = opts[:description], api
463
- @params = parse_parameters(opts[:params]) if opts[:params]
464
- @collection_operations = opts[:operations] if opts[:operations]
465
- @collection = opts[:collection]
466
- @operation = opts[:operation]
467
- self
468
- end
469
-
470
- def operations
471
- @collection_operations.collect { |o| api.documentation(@collection, o) }
472
- end
473
-
474
- class OperationParameter
475
- attr_reader :name
476
- attr_reader :type
477
- attr_reader :required
478
- attr_reader :description
479
-
480
- def initialize(data)
481
- @name, @type, @required, @description = data[:name], data[:type], data[:required], data[:description]
482
- end
483
-
484
- def to_comment
485
- " # @param [#{@type}, #{@name}] #{@description}"
486
- end
487
-
488
- end
489
-
490
- private
491
-
492
- def parse_parameters(params)
493
- params.collect { |p| OperationParameter.new(p) }
494
- end
495
-
496
- end
497
-
498
- module InstanceState
499
-
500
- class State
501
- attr_reader :name
502
- attr_reader :transitions
503
-
504
- def initialize(name)
505
- @name, @transitions = name, []
506
- end
507
- end
508
-
509
- class Transition
510
- attr_reader :to
511
- attr_reader :action
512
-
513
- def initialize(to, action)
514
- @to = to
515
- @action = action
516
- end
517
-
518
- def auto?
519
- @action.nil?
520
- end
521
- end
522
- end
523
-
524
- module HWP
525
-
526
- class Property
527
- attr_reader :name, :unit, :value, :kind
528
-
529
- def initialize(xml, name)
530
- @name, @kind, @value, @unit = xml['name'], xml['kind'].to_sym, xml['value'], xml['unit']
531
- declare_ranges(xml)
532
- self
533
- end
534
-
535
- def present?
536
- ! @value.nil?
537
- end
538
-
539
- private
540
-
541
- def declare_ranges(xml)
542
- case xml['kind']
543
- when 'range':
544
- self.class.instance_eval do
545
- attr_reader :range
546
- end
547
- @range = { :from => xml.xpath('range').first['first'], :to => xml.xpath('range').first['last'] }
548
- when 'enum':
549
- self.class.instance_eval do
550
- attr_reader :options
551
- end
552
- @options = xml.xpath('enum/entry').collect { |e| e['value'] }
553
- end
554
- end
555
-
556
- end
557
-
558
- # FloatProperty is like Property but return value is Float instead of String.
559
- class FloatProperty < Property
560
- def initialize(xml, name)
561
- super(xml, name)
562
- @value = @value.to_f if @value
563
- end
564
- end
565
- end
566
-
567
- end
568
-
569
- class String
570
-
571
- unless method_defined?(:classify)
572
- # Create a class name from string
573
- def classify
574
- self.singularize.camelize
575
- end
576
- end
577
-
578
- unless method_defined?(:camelize)
579
- # Camelize converts strings to UpperCamelCase
580
- def camelize
581
- self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
582
- end
583
- end
584
-
585
- unless method_defined?(:singularize)
586
- # Strip 's' character from end of string
587
- def singularize
588
- self.gsub(/s$/, '')
589
- end
590
- end
591
-
592
- # Convert string to float if string value seems like Float
593
- def convert
594
- return self.to_f if self.strip =~ /^([\d\.]+$)/
595
- self
596
- end
597
-
598
- # Simply converts whitespaces and - symbols to '_' which is safe for Ruby
599
- def sanitize
600
- self.gsub(/(\W+)/, '_')
601
- end
602
-
603
377
  end