deltacloud-client 0.0.9.8 → 0.1.0

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.
@@ -0,0 +1,282 @@
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)
176
+ else raise NoHandlerForMethod
177
+ end
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ def do_action(m)
184
+ @client.request(:"#{m[:method]}", m[:href], {}, {})
185
+ action_trigger(m[:rel])
186
+ end
187
+
188
+ end
189
+
190
+ class StateFullObject < ActionObject
191
+ attr_reader :state
192
+
193
+ def initialize(opts={}, &block)
194
+ super(opts)
195
+ @state = opts[:initial_state] || ''
196
+ add_default_states!
197
+ end
198
+
199
+ def add_default_states!
200
+ @objects << {
201
+ :method_name => 'stopped?',
202
+ :type => :state,
203
+ :state => 'STOPPED'
204
+ }
205
+ @objects << {
206
+ :method_name => 'running?',
207
+ :type => :state,
208
+ :state => 'RUNNING'
209
+ }
210
+ @objects << {
211
+ :method_name => 'pending?',
212
+ :type => :state,
213
+ :state => 'PENDING'
214
+ }
215
+ @objects << {
216
+ :method_name => 'shutting_down?',
217
+ :type => :state,
218
+ :state => 'SHUTTING_DOWN'
219
+ }
220
+ end
221
+
222
+ def action_trigger(action)
223
+ # Refresh object state after action
224
+ @new_state_object = @client.send(self.base_name, self.id)
225
+ @state = @new_state_object.state
226
+ self.update_actions!
227
+ end
228
+
229
+ alias :action_method_handler :method_handler
230
+
231
+ def method_handler(m, args=[])
232
+ begin
233
+ action_method_handler(m, args)
234
+ rescue NoHandlerForMethod
235
+ case m[:type]
236
+ when :state then evaluate_state(m[:state], @state)
237
+ else raise NoHandlerForMethod
238
+ end
239
+ end
240
+ end
241
+
242
+ # private
243
+
244
+ def evaluate_state(method_state, current_state)
245
+ method_state.eql?(current_state)
246
+ end
247
+
248
+ def action_objects
249
+ @objects.select { |o| o[:type] == :action_link }
250
+ end
251
+
252
+ def update_actions!
253
+ new_actions = @new_state_object.action_objects
254
+ @objects.reject! { |o| o[:type] == :action_link }
255
+ @objects = (@objects + new_actions)
256
+ end
257
+
258
+ end
259
+
260
+ def self.add_class(name, parent=:base)
261
+ parent_class = case parent
262
+ when :base then 'BaseObject'
263
+ when :action then 'ActionObject'
264
+ when :state then 'StateFullObject'
265
+ end
266
+ @defined_classes ||= []
267
+ if @defined_classes.include?(name)
268
+ DeltaCloud::API.class_eval("#{name.classify}")
269
+ else
270
+ DeltaCloud::API.class_eval("class #{name.classify} < DeltaCloud::#{parent_class}; end")
271
+ DeltaCloud::API.const_get("#{name.classify}")
272
+ end
273
+ end
274
+
275
+ def self.guess_model_type(response)
276
+ response = Nokogiri::XML(response.to_s)
277
+ return :action if ((response/'//actions').length == 1) and ((response/'//state').length == 0)
278
+ return :state if ((response/'//actions').length == 1) and ((response/'//state').length == 1)
279
+ return :base
280
+ end
281
+
282
+ 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,91 @@ 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
- request(:get, "/#{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)
98
+ request(:get, entry_points[model], args.first) do |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
- request(:get, "/#{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)
104
+ request(:get, "#{entry_points[model]}/#{args[0]}") do |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
202
- client.request(:"#{link['method']}", link['href'], {}, {})
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
+ # Deal with collections like public-addresses, private-addresses
170
+ if (attribute/'./*').length > 0
171
+ obj.add_collection!(attribute.name, (attribute/'*').collect { |value| value.text }) && next
172
+ end
173
+
174
+ # Anything else is treaten as text object
175
+ obj.add_text!(attribute.name, attribute.text.convert)
244
176
  end
177
+
245
178
  return obj
246
179
  end
247
180
 
@@ -252,73 +185,54 @@ module DeltaCloud
252
185
  api_xml = Nokogiri::XML(response)
253
186
  @driver_name = api_xml.xpath('/api').first['driver']
254
187
  @api_version = api_xml.xpath('/api').first['version']
255
- logger << "[API] Version #{@api_version}\n"
256
- logger << "[API] Driver #{@driver_name}\n"
188
+
257
189
  api_xml.css("api > link").each do |entry_point|
258
190
  rel, href = entry_point['rel'].to_sym, entry_point['href']
259
191
  @entry_points.store(rel, href)
260
- logger << "[API] Entry point '#{rel}' added\n"
192
+
261
193
  entry_point.css("feature").each do |feature|
262
194
  @features[rel] ||= []
263
195
  @features[rel] << feature['name'].to_sym
264
- logger << "[API] Feature #{feature['name']} added to #{rel}\n"
196
+
265
197
  end
266
198
  end
267
199
  end
268
200
  declare_entry_points_methods(@entry_points)
269
201
  end
270
202
 
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
203
+ # Generate create_* methods dynamically
283
204
  #
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
-
298
- params = {}
299
- ( params[:realm_id] = realm_id ) if realm_id
300
- ( params[:name] = name ) if name
301
- ( params[:user_data] = user_data ) if user_data
302
- ( params[:keyname] = key_name ) if key_name
303
-
304
- if opts[:hardware_profile].is_a?(String)
305
- params[:hwp_id] = opts[:hardware_profile]
306
- elsif opts[:hardware_profile].is_a?(Hash)
307
- opts[:hardware_profile].each do |k,v|
308
- params[:"hwp_#{k}"] = v
205
+ def method_missing(name, *args)
206
+ if name.to_s =~ /create_(\w+)/
207
+ params = args[0] if args[0] and args[0].class.eql?(Hash)
208
+ params ||= args[1] if args[1] and args[1].class.eql?(Hash)
209
+ params ||= {}
210
+
211
+ # FIXME: This fixes are related to Instance model and should be
212
+ # replaced by 'native' parameter names
213
+
214
+ params[:realm_id] ||= params[:realm] if params[:realm]
215
+ params[:keyname] ||= params[:key_name] if params[:key_name]
216
+
217
+ if params[:hardware_profile] and params[:hardware_profile].class.eql?(Hash)
218
+ params[:hardware_profile].each do |k,v|
219
+ params[:"hwp_#{k}"] ||= v
220
+ end
221
+ else
222
+ params[:hwp_id] ||= params[:hardware_profile]
309
223
  end
310
- end
311
224
 
312
- params[:image_id] = image_id
313
- instance = nil
225
+ params[:image_id] ||= params[:image_id] || args[0] if args[0].class!=Hash
314
226
 
315
- request(:post, entry_points[:instances], {}, params) do |response|
316
- c = DeltaCloud.define_class("Instance")
317
- instance = base_object(c, :instance, response)
318
- yield instance if block_given?
319
- end
227
+ obj = nil
320
228
 
321
- return instance
229
+ request(:post, entry_points[:"#{$1}s"], {}, params) do |response|
230
+ obj = base_object(:"#{$1}", response)
231
+ yield obj if block_given?
232
+ end
233
+ return obj
234
+ end
235
+ raise NoMethodError
322
236
  end
323
237
 
324
238
  # Basic request method
@@ -333,9 +247,10 @@ module DeltaCloud
333
247
  if conf[:query_args] != {}
334
248
  conf[:path] += '?' + URI.escape(conf[:query_args].collect{ |key, value| "#{key}=#{value}" }.join('&')).to_s
335
249
  end
336
- logger << "[#{conf[:method].to_s.upcase}] #{conf[:path]}\n"
250
+
337
251
  if conf[:method].eql?(:post)
338
252
  RestClient.send(:post, conf[:path], conf[:form_data], default_headers) do |response, request, block|
253
+ handle_backend_error(response) if response.code.eql?(500)
339
254
  if response.respond_to?('body')
340
255
  yield response.body if block_given?
341
256
  else
@@ -344,15 +259,41 @@ module DeltaCloud
344
259
  end
345
260
  else
346
261
  RestClient.send(conf[:method], conf[:path], default_headers) do |response, request, block|
347
- if response.respond_to?('body')
348
- yield response.body if block_given?
262
+ handle_backend_error(response) if response.code.eql?(500)
263
+ if conf[:method].eql?(:get) and [301, 302, 307].include? response.code
264
+ response.follow_redirection(request) do |response, request, block|
265
+ if response.respond_to?('body')
266
+ yield response.body if block_given?
267
+ else
268
+ yield response.to_s if block_given?
269
+ end
270
+ end
349
271
  else
350
- yield response.to_s if block_given?
272
+ if response.respond_to?('body')
273
+ yield response.body if block_given?
274
+ else
275
+ yield response.to_s if block_given?
276
+ end
351
277
  end
352
278
  end
353
279
  end
354
280
  end
355
281
 
282
+ # Re-raise backend errors as on exception in client with message from
283
+ # backend
284
+ class BackendError < Exception
285
+ def initialize(opts={})
286
+ @message = opts[:message]
287
+ end
288
+ def message
289
+ @message
290
+ end
291
+ end
292
+
293
+ def handle_backend_error(response)
294
+ raise BackendError.new(:message => (Nokogiri::XML(response)/'error/message').text)
295
+ end
296
+
356
297
  # Check if specified collection have wanted feature
357
298
  def feature?(collection, name)
358
299
  @features.has_key?(collection) && @features[collection].include?(name)
@@ -386,6 +327,7 @@ module DeltaCloud
386
327
  true if @entry_points!={}
387
328
  end
388
329
 
330
+ # This method will retrieve API documentation for given collection
389
331
  def documentation(collection, operation=nil)
390
332
  data = {}
391
333
  request(:get, "/docs/#{collection}") do |body|
@@ -426,151 +368,4 @@ module DeltaCloud
426
368
 
427
369
  end
428
370
 
429
- class Documentation
430
-
431
- attr_reader :api, :description, :params, :collection_operations
432
- attr_reader :collection, :operation
433
-
434
- def initialize(api, opts={})
435
- @description, @api = opts[:description], api
436
- @params = parse_parameters(opts[:params]) if opts[:params]
437
- @collection_operations = opts[:operations] if opts[:operations]
438
- @collection = opts[:collection]
439
- @operation = opts[:operation]
440
- self
441
- end
442
-
443
- def operations
444
- @collection_operations.collect { |o| api.documentation(@collection, o) }
445
- end
446
-
447
- class OperationParameter
448
- attr_reader :name
449
- attr_reader :type
450
- attr_reader :required
451
- attr_reader :description
452
-
453
- def initialize(data)
454
- @name, @type, @required, @description = data[:name], data[:type], data[:required], data[:description]
455
- end
456
-
457
- def to_comment
458
- " # @param [#{@type}, #{@name}] #{@description}"
459
- end
460
-
461
- end
462
-
463
- private
464
-
465
- def parse_parameters(params)
466
- params.collect { |p| OperationParameter.new(p) }
467
- end
468
-
469
- end
470
-
471
- module InstanceState
472
-
473
- class State
474
- attr_reader :name
475
- attr_reader :transitions
476
-
477
- def initialize(name)
478
- @name, @transitions = name, []
479
- end
480
- end
481
-
482
- class Transition
483
- attr_reader :to
484
- attr_reader :action
485
-
486
- def initialize(to, action)
487
- @to = to
488
- @action = action
489
- end
490
-
491
- def auto?
492
- @action.nil?
493
- end
494
- end
495
- end
496
-
497
- module HWP
498
-
499
- class Property
500
- attr_reader :name, :unit, :value, :kind
501
-
502
- def initialize(xml, name)
503
- @name, @kind, @value, @unit = xml['name'], xml['kind'].to_sym, xml['value'], xml['unit']
504
- declare_ranges(xml)
505
- self
506
- end
507
-
508
- def present?
509
- ! @value.nil?
510
- end
511
-
512
- private
513
-
514
- def declare_ranges(xml)
515
- case xml['kind']
516
- when 'range':
517
- self.class.instance_eval do
518
- attr_reader :range
519
- end
520
- @range = { :from => xml.xpath('range').first['first'], :to => xml.xpath('range').first['last'] }
521
- when 'enum':
522
- self.class.instance_eval do
523
- attr_reader :options
524
- end
525
- @options = xml.xpath('enum/entry').collect { |e| e['value'] }
526
- end
527
- end
528
-
529
- end
530
-
531
- # FloatProperty is like Property but return value is Float instead of String.
532
- class FloatProperty < Property
533
- def initialize(xml, name)
534
- super(xml, name)
535
- @value = @value.to_f if @value
536
- end
537
- end
538
- end
539
-
540
- end
541
-
542
- class String
543
-
544
- unless method_defined?(:classify)
545
- # Create a class name from string
546
- def classify
547
- self.singularize.camelize
548
- end
549
- end
550
-
551
- unless method_defined?(:camelize)
552
- # Camelize converts strings to UpperCamelCase
553
- def camelize
554
- self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
555
- end
556
- end
557
-
558
- unless method_defined?(:singularize)
559
- # Strip 's' character from end of string
560
- def singularize
561
- self.gsub(/s$/, '')
562
- end
563
- end
564
-
565
- # Convert string to float if string value seems like Float
566
- def convert
567
- return self.to_f if self.strip =~ /^([\d\.]+$)/
568
- self
569
- end
570
-
571
- # Simply converts whitespaces and - symbols to '_' which is safe for Ruby
572
- def sanitize
573
- self.gsub(/(\W+)/, '_')
574
- end
575
-
576
371
  end