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.
- data/lib/base_object.rb +282 -0
- data/lib/deltacloud.rb +125 -330
- data/lib/documentation.rb +50 -86
- data/lib/hwp_properties.rb +64 -0
- data/lib/instance_state.rb +29 -0
- data/lib/string.rb +53 -0
- data/specs/hardware_profiles_spec.rb +10 -8
- data/specs/images_spec.rb +15 -13
- data/specs/initialization_spec.rb +8 -6
- data/specs/instance_states_spec.rb +16 -14
- data/specs/instances_spec.rb +26 -23
- data/specs/realms_spec.rb +12 -10
- data/specs/spec_helper.rb +1 -0
- data/specs/storage_snapshot_spec.rb +11 -9
- data/specs/storage_volume_spec.rb +11 -9
- metadata +27 -24
data/lib/base_object.rb
ADDED
@@ -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
|
-
|
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,
|
109
|
-
|
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
|
-
|
102
|
+
|
116
103
|
define_method :"#{model.to_s.singularize}" do |*args|
|
117
|
-
request(:get, "
|
118
|
-
|
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
|
-
|
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(
|
134
|
-
|
135
|
-
|
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(
|
147
|
-
|
148
|
-
Nokogiri::XML(response).xpath("#{model.to_s.singularize}").
|
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(
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
192
|
+
|
261
193
|
entry_point.css("feature").each do |feature|
|
262
194
|
@features[rel] ||= []
|
263
195
|
@features[rel] << feature['name'].to_sym
|
264
|
-
|
196
|
+
|
265
197
|
end
|
266
198
|
end
|
267
199
|
end
|
268
200
|
declare_entry_points_methods(@entry_points)
|
269
201
|
end
|
270
202
|
|
271
|
-
|
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
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
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
|
-
|
313
|
-
instance = nil
|
225
|
+
params[:image_id] ||= params[:image_id] || args[0] if args[0].class!=Hash
|
314
226
|
|
315
|
-
|
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
|
-
|
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
|
-
|
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.
|
348
|
-
|
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
|
-
|
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
|