occi 2.5.3 → 2.5.4
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/Gemfile +1 -0
- data/Gemfile.lock +1 -0
- data/README.md +5 -1
- data/bin/occi +29 -0
- data/examples/dsl_example.rb +158 -0
- data/examples/x509auth_example.rb +90 -60
- data/lib/occi.rb +23 -10
- data/lib/occi/api/client.rb +595 -0
- data/lib/occi/api/dsl.rb +112 -0
- data/lib/occi/collection.rb +0 -3
- data/lib/occi/core.rb +21 -0
- data/lib/occi/core/entity.rb +3 -2
- data/lib/occi/log.rb +1 -2
- data/lib/occi/parser.rb +0 -11
- data/lib/occi/version.rb +1 -1
- data/lib/occiantlr/OCCIANTLR.g +10 -7
- data/lib/occiantlr/OCCIANTLR.tokens +14 -2
- data/lib/occiantlr/OCCIANTLRLexer.rb +584 -348
- data/lib/occiantlr/OCCIANTLRParser.rb +296 -221
- data/occi.gemspec +2 -1
- data/spec/occiantlr/parser_spec.rb +8 -8
- metadata +26 -5
- data/lib/occi/client.rb +0 -308
@@ -0,0 +1,595 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'httparty'
|
3
|
+
|
4
|
+
module OCCI
|
5
|
+
class Client
|
6
|
+
|
7
|
+
# HTTParty for raw HTTP requests
|
8
|
+
include HTTParty
|
9
|
+
headers 'Accept' => 'application/occi+json,text/plain;q=0.5'
|
10
|
+
|
11
|
+
# a few attributes which should be visible outside the client
|
12
|
+
attr_reader :endpoint
|
13
|
+
attr_reader :auth_options
|
14
|
+
attr_reader :media_type
|
15
|
+
attr_reader :connected
|
16
|
+
attr_reader :model
|
17
|
+
|
18
|
+
# hash mapping human-readable resource names to OCCI identifiers
|
19
|
+
# TODO: get resources dynamically from the model
|
20
|
+
RESOURCES = {
|
21
|
+
:compute => "http://schemas.ogf.org/occi/infrastructure#compute",
|
22
|
+
:storage => "http://schemas.ogf.org/occi/infrastructure#storage",
|
23
|
+
:network => "http://schemas.ogf.org/occi/infrastructure#network"
|
24
|
+
}
|
25
|
+
|
26
|
+
# hash mapping HTTP response codes to human-readable messages
|
27
|
+
HTTP_CODES = {
|
28
|
+
"100" => "Continue",
|
29
|
+
"101" => "Switching Protocols",
|
30
|
+
"200" => "OK",
|
31
|
+
"201" => "Created",
|
32
|
+
"202" => "Accepted",
|
33
|
+
"203" => "Non-Authoritative Information",
|
34
|
+
"204" => "No Content",
|
35
|
+
"205" => "Reset Content",
|
36
|
+
"206" => "Partial Content",
|
37
|
+
"300" => "Multiple Choices",
|
38
|
+
"301" => "Moved Permanently",
|
39
|
+
"302" => "Found",
|
40
|
+
"303" => "See Other",
|
41
|
+
"304" => "Not Modified",
|
42
|
+
"305" => "Use Proxy",
|
43
|
+
"307" => "Temporary Redirect",
|
44
|
+
"400" => "Bad Request",
|
45
|
+
"401" => "Unauthorized",
|
46
|
+
"402" => "Payment Required",
|
47
|
+
"403" => "Forbidden",
|
48
|
+
"404" => "Not Found",
|
49
|
+
"405" => "Method Not Allowed",
|
50
|
+
"406" => "Not Acceptable",
|
51
|
+
"407" => "Proxy Authentication Required",
|
52
|
+
"408" => "Request Time-out",
|
53
|
+
"409" => "Conflict",
|
54
|
+
"410" => "Gone",
|
55
|
+
"411" => "Length Required",
|
56
|
+
"412" => "Precondition Failed",
|
57
|
+
"413" => "Request Entity Too Large",
|
58
|
+
"414" => "Request-URI Too Large",
|
59
|
+
"415" => "Unsupported Media Type",
|
60
|
+
"416" => "Requested range not satisfiable",
|
61
|
+
"417" => "Expectation Failed",
|
62
|
+
"500" => "Internal Server Error",
|
63
|
+
"501" => "Not Implemented",
|
64
|
+
"502" => "Bad Gateway",
|
65
|
+
"503" => "Service Unavailable",
|
66
|
+
"504" => "Gateway Time-out",
|
67
|
+
"505" => "HTTP Version not supported"
|
68
|
+
}
|
69
|
+
|
70
|
+
# @param [String] Endpoint URI
|
71
|
+
# @param [Hash] Auth options
|
72
|
+
# @param [Hash] Logging options
|
73
|
+
# @param [Boolean] Enable autoconnect?
|
74
|
+
# @return [OCCI:Client] Client instance
|
75
|
+
def initialize(endpoint = "http://localhost:3000/", auth_options = {:type => "none"}, log_options = { :out => STDERR, :level => OCCI::Log::WARN, :logger => nil}, auto_connect = true, media_type = nil)
|
76
|
+
# set OCCI::Log
|
77
|
+
set_logger log_options
|
78
|
+
|
79
|
+
# pass auth options to HTTParty
|
80
|
+
change_auth auth_options
|
81
|
+
|
82
|
+
# check the validity and canonize the endpoint URI
|
83
|
+
prepare_endpoint endpoint
|
84
|
+
|
85
|
+
# get accepted media types from HTTParty
|
86
|
+
set_media_type
|
87
|
+
|
88
|
+
# force media_type if provided
|
89
|
+
if media_type
|
90
|
+
self.class.headers 'Accept' => media_type
|
91
|
+
@media_type = media_type
|
92
|
+
end
|
93
|
+
|
94
|
+
OCCI::Log.debug("Media Type: #{@media_type}")
|
95
|
+
OCCI::Log.debug("Headers: #{self.class.headers}")
|
96
|
+
|
97
|
+
# get model information from the endpoint
|
98
|
+
# and create OCCI::Model instance
|
99
|
+
set_model
|
100
|
+
|
101
|
+
# auto-connect?
|
102
|
+
@connected = auto_connect
|
103
|
+
end
|
104
|
+
|
105
|
+
# @param [String] Resource name or resource identifier
|
106
|
+
# @return [OCCI::Core::Entity] Resource instance
|
107
|
+
def get_resource(resource_type)
|
108
|
+
|
109
|
+
OCCI::Log.debug("Instantiating #{resource_type} ...")
|
110
|
+
|
111
|
+
if RESOURCES.has_value? resource_type
|
112
|
+
# we got a resource type identifier
|
113
|
+
OCCI::Core::Resource.new resource_type
|
114
|
+
elsif RESOURCES.has_key? resource_type.to_sym
|
115
|
+
# we got a resource type name
|
116
|
+
OCCI::Core::Resource.new get_resource_type_identifier(resource_type)
|
117
|
+
else
|
118
|
+
raise "Unknown resource type! [#{resource_type}]"
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return [Array] List of available resource types in a human-readable format
|
124
|
+
def get_resource_types
|
125
|
+
OCCI::Log.debug("Getting resource types ...")
|
126
|
+
RESOURCES.keys.map! { |k| k.to_s }
|
127
|
+
end
|
128
|
+
|
129
|
+
# @return [Array] List of available resource types in a OCCI ID format
|
130
|
+
def get_resource_type_identifiers
|
131
|
+
OCCI::Log.debug("Getting resource identifiers ...")
|
132
|
+
RESOURCES.values
|
133
|
+
end
|
134
|
+
|
135
|
+
# @param [String] Name of the mixin
|
136
|
+
# @param [String] Type of the mixin
|
137
|
+
# @param [Boolean] Should we describe the mixin or return its link?
|
138
|
+
# @return [String, OCCI:Collection] Link or mixin description
|
139
|
+
def find_mixin(name, type = nil, describe = false)
|
140
|
+
|
141
|
+
OCCI::Log.debug("Looking for mixin #{name} + #{type} + #{describe}")
|
142
|
+
|
143
|
+
# is type valid?
|
144
|
+
unless type.nil?
|
145
|
+
raise "Unknown mixin type! [#{type}]" unless @mixins.has_key? type.to_sym
|
146
|
+
end
|
147
|
+
|
148
|
+
# TODO: extend this code to support multiple matches and regex filters
|
149
|
+
# should we look for links or descriptions?
|
150
|
+
unless describe
|
151
|
+
# we are looking for links
|
152
|
+
# prefix mixin name with '#' to simplify the search
|
153
|
+
name = "#" + name
|
154
|
+
unless type
|
155
|
+
# there is no type preference, return first global match
|
156
|
+
@mixins.flatten(2).select { |mixin| mixin.to_s.reverse.start_with? name.reverse }.first
|
157
|
+
else
|
158
|
+
# return the first match with the selected type
|
159
|
+
@mixins[type.to_sym].select { |mixin| mixin.to_s.reverse.start_with? name.reverse }.first
|
160
|
+
end
|
161
|
+
else
|
162
|
+
# we are looking for descriptions
|
163
|
+
unless type
|
164
|
+
# try in os_tpls first
|
165
|
+
found = get_os_templates.select { |mixin| mixin.term == name }.first
|
166
|
+
|
167
|
+
# then try in resource_tpls
|
168
|
+
unless found
|
169
|
+
found = get_resource_templates.select { |template| template.term == name }.first
|
170
|
+
end
|
171
|
+
|
172
|
+
found
|
173
|
+
else
|
174
|
+
# get the first match from either os_tpls or resource_tpls
|
175
|
+
case
|
176
|
+
when type == "os_tpl"
|
177
|
+
get_os_templates.select { |mixin| mixin.term == name }.first
|
178
|
+
when type == "resource_tpl"
|
179
|
+
get_resource_templates.select { |template| template.term == name }.first
|
180
|
+
else
|
181
|
+
nil
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# @param [String] Type of mixins to return
|
188
|
+
# @return [Array] List of available mixins
|
189
|
+
def get_mixins(type = nil)
|
190
|
+
unless type.nil?
|
191
|
+
# is type valid?
|
192
|
+
raise "Unknown mixin type! #{type}" unless @mixins.has_key? type.to_sym
|
193
|
+
|
194
|
+
# return mixin of the selected type
|
195
|
+
@mixins[type.to_sym]
|
196
|
+
else
|
197
|
+
# we did not get a type, return all mixins
|
198
|
+
mixins = []
|
199
|
+
|
200
|
+
# flatten the hash and remove its keys
|
201
|
+
get_mixin_types.each do |type|
|
202
|
+
mixins.concat @mixins[type.to_sym]
|
203
|
+
end
|
204
|
+
|
205
|
+
mixins
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# @return [Array] List of available mixin types
|
210
|
+
def get_mixin_types
|
211
|
+
@mixins.keys.map! { |k| k.to_s }
|
212
|
+
end
|
213
|
+
|
214
|
+
# @return [Array] List of available mixin type identifiers
|
215
|
+
def get_mixin_type_identifiers
|
216
|
+
identifiers = []
|
217
|
+
|
218
|
+
get_mixin_types.each do |mixin_type|
|
219
|
+
identifiers << 'http://schemas.ogf.org/occi/infrastructure#' + mixin_type
|
220
|
+
end
|
221
|
+
|
222
|
+
identifiers
|
223
|
+
end
|
224
|
+
|
225
|
+
# @param [String] Human-readable name of the resource
|
226
|
+
# @return [String] OCCI resource identifier
|
227
|
+
def get_resource_type_identifier(resource_type)
|
228
|
+
raise "Unknown resource type! [#{resource_type}]" unless RESOURCES.has_key? resource_type.to_sym
|
229
|
+
|
230
|
+
RESOURCES[resource_type.to_sym]
|
231
|
+
end
|
232
|
+
|
233
|
+
# @param [String] OCCI resource identifier
|
234
|
+
# @return [String] Human-readable name of the resource
|
235
|
+
def get_resource_type(resource_type_identifier)
|
236
|
+
raise "Unknown resource type identifier! [#{resource_type_identifier}]" unless RESOURCES.has_value? resource_type_identifier
|
237
|
+
|
238
|
+
RESOURCES.key(resource_type_identifier).to_s
|
239
|
+
end
|
240
|
+
|
241
|
+
# @param [String] OCCI resource type identifier or just type
|
242
|
+
# @return [Array] List of links
|
243
|
+
def list(resource_type_identifier)
|
244
|
+
|
245
|
+
# convert type to type identifier
|
246
|
+
unless resource_type_identifier.start_with? "http://" or resource_type_identifier.start_with? "https://"
|
247
|
+
resource_type_identifier = get_resource_type_identifier resource_type_identifier
|
248
|
+
end
|
249
|
+
|
250
|
+
# check some basic pre-conditions
|
251
|
+
raise "Endpoint is not connected!" unless @connected
|
252
|
+
raise "Unkown resource type identifier! [#{resource_type_identifier}]" unless RESOURCES.has_value? resource_type_identifier
|
253
|
+
|
254
|
+
# split the type identifier and get the most important part
|
255
|
+
uri_part = resource_type_identifier.split('#').last
|
256
|
+
|
257
|
+
list = []
|
258
|
+
|
259
|
+
# request uri-list from the server
|
260
|
+
path = uri_part + '/'
|
261
|
+
list = self.class.get(@endpoint + path, :headers => { "Accept" => 'text/uri-list' }).body.split("\n").compact
|
262
|
+
|
263
|
+
list
|
264
|
+
end
|
265
|
+
|
266
|
+
# @param [String] OCCI resource type identifier or just type
|
267
|
+
# @return [OCCI::Collection] List of descriptions
|
268
|
+
def describe(resource_identifier)
|
269
|
+
|
270
|
+
# convert type to type identifier
|
271
|
+
unless resource_identifier.start_with? "http://" or resource_identifier.start_with? "https://"
|
272
|
+
resource_identifier = get_resource_type_identifier resource_identifier
|
273
|
+
end
|
274
|
+
|
275
|
+
# check some basic pre-conditions
|
276
|
+
raise "Endpoint is not connected!" unless @connected
|
277
|
+
|
278
|
+
descriptions = nil
|
279
|
+
|
280
|
+
if RESOURCES.has_value? resource_identifier
|
281
|
+
# we got type identifier
|
282
|
+
# split the type identifier
|
283
|
+
uri_part = resource_identifier.split('#').last
|
284
|
+
# make the request
|
285
|
+
descriptions = get(uri_part + '/')
|
286
|
+
elsif resource_identifier.start_with? @endpoint
|
287
|
+
# we got resource link
|
288
|
+
# make the request
|
289
|
+
descriptions = get(sanitize_resource_link(resource_identifier))
|
290
|
+
else
|
291
|
+
raise "Unkown resource identifier! [#{resource_identifier}]"
|
292
|
+
end
|
293
|
+
|
294
|
+
descriptions
|
295
|
+
end
|
296
|
+
|
297
|
+
# @param [OCCI::Core::Entity] Entity to be created on the server
|
298
|
+
# @return [String] Link (URI) or the new resource
|
299
|
+
def create(entity)
|
300
|
+
|
301
|
+
# check some basic pre-conditions
|
302
|
+
raise "Endpoint is not connected!" unless @connected
|
303
|
+
raise "#{entity} not an entity" unless entity.kind_of? OCCI::Core::Entity
|
304
|
+
|
305
|
+
# is this entity valid?
|
306
|
+
entity.check(@model)
|
307
|
+
kind = @model.get_by_id(entity.kind)
|
308
|
+
raise "No kind found for #{entity}" unless kind
|
309
|
+
|
310
|
+
# get location for this kind of entity
|
311
|
+
location = @model.get_by_id(entity.kind).location
|
312
|
+
collection = OCCI::Collection.new
|
313
|
+
|
314
|
+
# is this entity a Resource or a Link?
|
315
|
+
collection.resources << entity if entity.kind_of? OCCI::Core::Resource
|
316
|
+
collection.links << entity if entity.kind_of? OCCI::Core::Link
|
317
|
+
|
318
|
+
# make the request
|
319
|
+
post location, collection
|
320
|
+
end
|
321
|
+
|
322
|
+
# @param [String] Resource link (URI)
|
323
|
+
# @return [Boolean] Success?
|
324
|
+
def delete(resource_identifier)
|
325
|
+
# TODO: delete should work for entire resource types
|
326
|
+
# check some basic pre-conditions
|
327
|
+
raise "Endpoint is not connected!" unless @connected
|
328
|
+
raise "Unknown resource identifier! #{resource_identifier}" unless resource_identifier.start_with? @endpoint
|
329
|
+
|
330
|
+
# make the request
|
331
|
+
del(sanitize_resource_link(resource_identifier))
|
332
|
+
end
|
333
|
+
|
334
|
+
# @param [String] Resource link (URI)
|
335
|
+
# @param [String] Type of action
|
336
|
+
# @return [String] Resource link (URI)
|
337
|
+
def trigger(resource_identifier, action)
|
338
|
+
# TODO: not tested
|
339
|
+
# check some basic pre-conditions
|
340
|
+
raise "Endpoint is not connected!" unless @connected
|
341
|
+
raise "Unknown resource identifier! #{resource_identifier}" unless resource_identifier.start_with? @endpoint
|
342
|
+
|
343
|
+
# encapsulate the acion in a collection
|
344
|
+
collection = OCCI::Collection.new
|
345
|
+
collection.actions << action
|
346
|
+
|
347
|
+
# make the request
|
348
|
+
post sanitize_resource_link(resource_identifier), collection
|
349
|
+
end
|
350
|
+
|
351
|
+
def refresh
|
352
|
+
# re-download the model from the server
|
353
|
+
set_model
|
354
|
+
end
|
355
|
+
|
356
|
+
# @param [OCCI::Core::Resource] Compute instance
|
357
|
+
# @param [URI,String] Storage location (URI)
|
358
|
+
# @param [OCCI::Core::Attributes] Attributes
|
359
|
+
# @param [Array] Mixins
|
360
|
+
# @return [OCCI::Core::Link] Link instance
|
361
|
+
def storagelink(compute, storage_location, attributes=OCCI::Core::Attributes.new, mixins=[])
|
362
|
+
kind = 'http://schemas.ogf.org/occi/infrastructure#storagelink'
|
363
|
+
storage_kind = 'http://schemas.ogf.org/occi/infrastructure#storage'
|
364
|
+
storagelink = link(kind, compute, storage_location, storage_kind, attributes, mixins)
|
365
|
+
|
366
|
+
storagelink
|
367
|
+
end
|
368
|
+
|
369
|
+
# @param [OCCI::Core::Resource] Compute instance
|
370
|
+
# @param [URI,String] Network location (URI)
|
371
|
+
# @param [OCCI::Core::Attributes] Attributes
|
372
|
+
# @param [Array] Mixins
|
373
|
+
# @return [OCCI::Core::Link] Link instance
|
374
|
+
def networkinterface(compute, network_location, attributes=OCCI::Core::Attributes.new, mixins=[])
|
375
|
+
kind = 'http://schemas.ogf.org/occi/infrastructure#networkinterface'
|
376
|
+
network_kind = 'http://schemas.ogf.org/occi/infrastructure#network'
|
377
|
+
networkinterface = link(kind, compute, network_location, network_kind, attributes, mixins)
|
378
|
+
|
379
|
+
networkinterface
|
380
|
+
end
|
381
|
+
|
382
|
+
#private
|
383
|
+
|
384
|
+
# @param [Hash]
|
385
|
+
def set_logger(log_options)
|
386
|
+
|
387
|
+
if log_options[:logger].nil? or not (log_options[:logger].kind_of? OCCI::Log)
|
388
|
+
logger = OCCI::Log.new(log_options[:out])
|
389
|
+
logger.level = log_options[:level]
|
390
|
+
end
|
391
|
+
|
392
|
+
self.class.debug_output $stderr if log_options[:level] == OCCI::Log::DEBUG
|
393
|
+
|
394
|
+
end
|
395
|
+
|
396
|
+
# @param [Hash]
|
397
|
+
def change_auth(auth_options)
|
398
|
+
@auth_options = auth_options
|
399
|
+
|
400
|
+
case @auth_options[:type]
|
401
|
+
when "basic"
|
402
|
+
# set up basic auth
|
403
|
+
raise ArgumentError, "Missing required options 'username' and 'password' for basic auth!" unless @auth_options[:username] and @auth_options[:password]
|
404
|
+
self.class.basic_auth @auth_options[:username], @auth_options[:password]
|
405
|
+
when "digest"
|
406
|
+
# set up digest auth
|
407
|
+
raise ArgumentError, "Missing required options 'username' and 'password' for digest auth!" unless @auth_options[:username] and @auth_options[:password]
|
408
|
+
self.class.digest_auth @auth_options[:username], @auth_options[:password]
|
409
|
+
when "x509"
|
410
|
+
# set up pem and optionally pem_password and ssl_ca_path
|
411
|
+
raise ArgumentError, "Missing required option 'user_cert' for x509 auth!" unless @auth_options[:user_cert]
|
412
|
+
raise ArgumentError, "The file specified in 'user_cert' does not exist!" unless File.exists? @auth_options[:user_cert]
|
413
|
+
|
414
|
+
self.class.pem File.read(@auth_options[:user_cert]), @auth_options[:user_cert_password]
|
415
|
+
self.class.ssl_ca_path @auth_options[:ca_path] unless @auth_options[:ca_path].nil? or @auth_options[:ca_path].empty?
|
416
|
+
when "none", nil
|
417
|
+
# do nothing
|
418
|
+
else
|
419
|
+
raise ArgumentError, "Unknown AUTH method [#{@auth_options[:type]}]!"
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
# @param [String]
|
424
|
+
# @param [OCCI::Collection]
|
425
|
+
# @return [OCCI::Collection]
|
426
|
+
def get(path='', filter=nil)
|
427
|
+
path = path.reverse.chomp('/').reverse
|
428
|
+
response = if filter
|
429
|
+
categories = filter.categories.collect { |category| category.to_text }.join(',')
|
430
|
+
attributes = filter.entities.collect { |entity| entity.attributes.combine.collect { |k, v| k + '=' + v } }.join(',')
|
431
|
+
headers = self.class.headers.clone
|
432
|
+
headers['Content-Type'] = 'text/occi'
|
433
|
+
headers['Category'] = categories unless categories.empty?
|
434
|
+
headers['X-OCCI-Attributes'] = attributes unless attributes.empty?
|
435
|
+
self.class.get(@endpoint + path,
|
436
|
+
:headers => headers)
|
437
|
+
else
|
438
|
+
self.class.get(@endpoint + path)
|
439
|
+
end
|
440
|
+
|
441
|
+
response_msg = response_message response
|
442
|
+
raise "HTTP GET failed! #{response_msg}" unless response.code.between? 200, 300
|
443
|
+
|
444
|
+
kind = @model.get_by_location path if @model
|
445
|
+
kind ? entity_type = kind.entity_type : entity_type = nil
|
446
|
+
_, collection = OCCI::Parser.parse(response.content_type, response.body, path.include?('-/'), entity_type)
|
447
|
+
|
448
|
+
collection
|
449
|
+
end
|
450
|
+
|
451
|
+
# @param [String]
|
452
|
+
# @param [OCCI::Collection]
|
453
|
+
# @return [String]
|
454
|
+
def post(path, collection)
|
455
|
+
path = path.reverse.chomp('/').reverse
|
456
|
+
response = if @media_type == 'application/occi+json'
|
457
|
+
self.class.post(@endpoint + path,
|
458
|
+
:body => collection.to_json,
|
459
|
+
:headers => { 'Accept' => 'text/uri-list', 'Content-Type' => 'application/occi+json' })
|
460
|
+
else
|
461
|
+
self.class.post(@endpoint + path,
|
462
|
+
:body => collection.to_text,
|
463
|
+
:headers => { 'Accept' => 'text/uri-list', 'Content-Type' => 'text/plain' })
|
464
|
+
end
|
465
|
+
|
466
|
+
response_msg = response_message response
|
467
|
+
raise "HTTP POST failed! #{response_msg}" unless response.code.between? 200, 300
|
468
|
+
|
469
|
+
URI.parse(response.body).to_s
|
470
|
+
end
|
471
|
+
|
472
|
+
# @param [String]
|
473
|
+
# @param [OCCI::Collection]
|
474
|
+
# @return [OCCI::Collection]
|
475
|
+
def put(path, collection)
|
476
|
+
path = path.reverse.chomp('/').reverse
|
477
|
+
response = if @media_type == 'application/occi+json'
|
478
|
+
self.class.post(@endpoint + path, :body => collection.to_json, :headers => { 'Content-Type' => 'application/occi+json' })
|
479
|
+
else
|
480
|
+
self.class.post(@endpoint + path, { :body => collection.to_text, :headers => { 'Content-Type' => 'text/plain' } })
|
481
|
+
end
|
482
|
+
|
483
|
+
response_msg = response_message response
|
484
|
+
raise "HTTP PUT failed! #{response_msg}" unless response.code.between? 200, 300
|
485
|
+
|
486
|
+
_, collection = OCCI::Parser.parse(response.content_type, response.body)
|
487
|
+
|
488
|
+
collection
|
489
|
+
end
|
490
|
+
|
491
|
+
# @param [String]
|
492
|
+
# @param [OCCI::Collection]
|
493
|
+
# @return [Boolean]
|
494
|
+
def del(path, collection=nil)
|
495
|
+
path = path.reverse.chomp('/').reverse
|
496
|
+
response = self.class.delete(@endpoint + path)
|
497
|
+
|
498
|
+
response_msg = response_message response
|
499
|
+
raise "HTTP DELETE failed! #{response_msg}" unless response.code.between? 200, 300
|
500
|
+
|
501
|
+
true
|
502
|
+
end
|
503
|
+
|
504
|
+
# @param [String]
|
505
|
+
# @param [OCCI::Core::Resource]
|
506
|
+
# @param [URI,String]
|
507
|
+
# @param [String]
|
508
|
+
# @param [OCCI::Core::Attributes]
|
509
|
+
# @param [Array]
|
510
|
+
# @return [OCCI::Core::Link]
|
511
|
+
def link(kind, source, target_location, target_kind, attributes=OCCI::Core::Attributes.new, mixins=[])
|
512
|
+
link = OCCI::Core::Link.new(kind)
|
513
|
+
link.mixins = mixins
|
514
|
+
link.attributes = attributes
|
515
|
+
link.target = (target_location.kind_of? URI::Generic) ? target_location.path : target_location.to_s
|
516
|
+
link.rel = target_kind
|
517
|
+
|
518
|
+
jj link
|
519
|
+
link.check @model
|
520
|
+
source.links << link
|
521
|
+
link
|
522
|
+
end
|
523
|
+
|
524
|
+
# @param [String]
|
525
|
+
# @return [String]
|
526
|
+
def prepare_endpoint(endpoint)
|
527
|
+
raise 'Endpoint not a valid URI' if (endpoint =~ URI::ABS_URI).nil?
|
528
|
+
@endpoint = endpoint.chomp('/') + '/'
|
529
|
+
end
|
530
|
+
|
531
|
+
# @param [String]
|
532
|
+
# @return [String]
|
533
|
+
def sanitize_resource_link(resource_link)
|
534
|
+
raise "Resource link #{resource_link} is not valid!" unless resource_link.start_with? @endpoint
|
535
|
+
|
536
|
+
resource_link.gsub @endpoint, '/'
|
537
|
+
end
|
538
|
+
|
539
|
+
def set_model
|
540
|
+
|
541
|
+
#
|
542
|
+
model = get('/-/')
|
543
|
+
@model = OCCI::Model.new(model)
|
544
|
+
|
545
|
+
@mixins = {
|
546
|
+
:os_tpl => [],
|
547
|
+
:resource_tpl => []
|
548
|
+
}
|
549
|
+
|
550
|
+
#
|
551
|
+
get_os_templates.each do |os_tpl|
|
552
|
+
@mixins[:os_tpl] << os_tpl.type_identifier unless os_tpl.nil? or os_tpl.type_identifier.nil?
|
553
|
+
end
|
554
|
+
|
555
|
+
#
|
556
|
+
get_resource_templates.each do |res_tpl|
|
557
|
+
@mixins[:resource_tpl] << res_tpl.type_identifier unless res_tpl.nil? or res_tpl.type_identifier.nil?
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
# @return [OCCI::Collection] collection including all registered OS templates
|
562
|
+
def get_os_templates
|
563
|
+
@model.get.mixins.select { |mixin| mixin.related.select { |rel| rel.end_with? 'os_tpl' }.any? }
|
564
|
+
end
|
565
|
+
|
566
|
+
# @return [OCCI::Collection] collection including all registered resource templates
|
567
|
+
def get_resource_templates
|
568
|
+
@model.get.mixins.select { |mixin| mixin.related.select { |rel| rel.end_with? 'resource_tpl' }.any? }
|
569
|
+
end
|
570
|
+
|
571
|
+
# @return [String]
|
572
|
+
def set_media_type
|
573
|
+
media_types = self.class.head(@endpoint).headers['accept']
|
574
|
+
OCCI::Log.debug("Available media types: #{media_types}")
|
575
|
+
@media_type = case media_types
|
576
|
+
when /application\/occi\+json/
|
577
|
+
'application/occi+json'
|
578
|
+
else
|
579
|
+
'text/plain'
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
# @param [HTTParty::Response]
|
584
|
+
def response_message(response)
|
585
|
+
'HTTP Response status: [' + response.code.to_s + '] ' + reason_phrase(response.code)
|
586
|
+
end
|
587
|
+
|
588
|
+
# @param [Integer]
|
589
|
+
# @return [String]
|
590
|
+
def reason_phrase(code)
|
591
|
+
HTTP_CODES[code.to_s]
|
592
|
+
end
|
593
|
+
|
594
|
+
end
|
595
|
+
end
|