steamcannon-deltacloud-client 0.0.9.8.1 → 0.1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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