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 +3 -9
- data/bin/deltacloudc +1 -0
- data/lib/base_object.rb +287 -0
- data/lib/deltacloud.rb +114 -340
- 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 +1 -1
- data/specs/instances_spec.rb +2 -2
- data/specs/storage_volume_spec.rb +0 -2
- metadata +13 -34
- data/specs/data/images/img1.yml +0 -4
- data/specs/data/images/img2.yml +0 -4
- data/specs/data/images/img3.yml +0 -4
- data/specs/data/instances/inst0.yml +0 -16
- data/specs/data/instances/inst1.yml +0 -9
- data/specs/data/instances/inst2.yml +0 -9
- data/specs/data/storage_snapshots/snap1.yml +0 -4
- data/specs/data/storage_snapshots/snap2.yml +0 -4
- data/specs/data/storage_snapshots/snap3.yml +0 -4
- data/specs/data/storage_volumes/vol1.yml +0 -6
- data/specs/data/storage_volumes/vol2.yml +0 -6
- data/specs/data/storage_volumes/vol3.yml +0 -6
data/Rakefile
CHANGED
@@ -25,15 +25,9 @@ task 'documentation' do
|
|
25
25
|
load 'lib/documentation.rb'
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
data/lib/base_object.rb
ADDED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
102
|
+
|
116
103
|
define_method :"#{model.to_s.singularize}" do |*args|
|
117
104
|
request(:get, "#{entry_points[model]}/#{args[0]}") do |response|
|
118
|
-
|
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
|
-
|
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 |*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
|
-
|
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
|
-
|
198
|
+
|
261
199
|
entry_point.css("feature").each do |feature|
|
262
200
|
@features[rel] ||= []
|
263
201
|
@features[rel] << feature['name'].to_sym
|
264
|
-
|
202
|
+
|
265
203
|
end
|
266
204
|
end
|
267
205
|
end
|
268
206
|
declare_entry_points_methods(@entry_points)
|
269
207
|
end
|
270
208
|
|
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
|
209
|
+
# Generate create_* methods dynamically
|
283
210
|
#
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
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
|
-
|
315
|
-
|
217
|
+
# FIXME: This fixes are related to Instance model and should be
|
218
|
+
# replaced by 'native' parameter names
|
316
219
|
|
317
|
-
|
318
|
-
|
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
|
-
|
324
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|