occi-api 4.0.0.alpha.1
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/.gitignore +15 -0
- data/.rspec +1 -0
- data/.travis.yml +43 -0
- data/.yardopts +1 -0
- data/AUTHORS +9 -0
- data/Gemfile +16 -0
- data/LICENSE +13 -0
- data/README.md +344 -0
- data/Rakefile +37 -0
- data/examples/dsl_example.rb +167 -0
- data/examples/x509auth_example.rb +161 -0
- data/ext/mkrf_conf.rb +34 -0
- data/features/cassettes/Create_an_OCCI_Resource/_http_http___141_5_99_69__text_plain_201_.yml +288 -0
- data/features/cassettes/Delete_an_OCCI_Resource/_http_http___141_5_99_69__text_plain_201_.yml +288 -0
- data/features/cassettes/Discovery_Interface/Retrieving_all_OCCI_Categories_supported_by_the_OCCI_Server/_http_http___141_5_99_69__application_json_200_.yml +333 -0
- data/features/cassettes/Discovery_Interface/Retrieving_all_OCCI_Categories_supported_by_the_OCCI_Server/_http_http___141_5_99_69__text_plain_200_.yml +529 -0
- data/features/cassettes/Discovery_Interface/Retrieving_all_OCCI_Categories_supported_by_the_OCCI_Server/_http_http___141_5_99_69__text_plain_200_action_.yml +288 -0
- data/features/cassettes/Miscellaneous_operation_on_an_OCCI_Resource/_http_http___141_5_99_69__text_plain_201_.yml +288 -0
- data/features/cassettes/Read_an_OCCI_Resource/_http_http___141_5_99_69__text_plain_201_.yml +288 -0
- data/features/cassettes/Update_an_OCCI_Resource/_http_http___141_5_99_69__text_plain_201_.yml +288 -0
- data/features/common/step_definitions/common_steps.rb +32 -0
- data/features/occi/core/create/create.feature +18 -0
- data/features/occi/core/create/step_definitions/create_steps.rb +0 -0
- data/features/occi/core/delete/delete.feature +18 -0
- data/features/occi/core/delete/step_definitions/delete_steps.rb +0 -0
- data/features/occi/core/discovery_interface/discovery_interface.feature +37 -0
- data/features/occi/core/discovery_interface/step_definitions/discovery_interface_steps.rb +19 -0
- data/features/occi/core/miscellaneous/miscellaneous.feature +18 -0
- data/features/occi/core/miscellaneous/step_definitions/miscellaneous_steps.rb +0 -0
- data/features/occi/core/read/read.feature +18 -0
- data/features/occi/core/read/step_definitions/read_steps.rb +0 -0
- data/features/occi/core/update/step_definitions/update_steps.rb +0 -0
- data/features/occi/core/update/update.feature +18 -0
- data/features/occi/infrastructure/create/create.feature +18 -0
- data/features/occi/infrastructure/create/step_definitions/create_steps.rb +0 -0
- data/features/support/env.rb +16 -0
- data/lib/occi/api/client/client_amqp.rb +766 -0
- data/lib/occi/api/client/client_base.rb +689 -0
- data/lib/occi/api/client/client_http.rb +541 -0
- data/lib/occi/api/client/errors/authn_error.rb +7 -0
- data/lib/occi/api/client/errors.rb +1 -0
- data/lib/occi/api/client/http/authn_plugins/base.rb +27 -0
- data/lib/occi/api/client/http/authn_plugins/basic.rb +22 -0
- data/lib/occi/api/client/http/authn_plugins/digest.rb +22 -0
- data/lib/occi/api/client/http/authn_plugins/dummy.rb +13 -0
- data/lib/occi/api/client/http/authn_plugins/keystone.rb +61 -0
- data/lib/occi/api/client/http/authn_plugins/x509.rb +46 -0
- data/lib/occi/api/client/http/authn_plugins.rb +6 -0
- data/lib/occi/api/client/http/authn_utils.rb +87 -0
- data/lib/occi/api/client/http/httparty_fix.rb +53 -0
- data/lib/occi/api/client/http/net_http_fix.rb +21 -0
- data/lib/occi/api/dsl.rb +146 -0
- data/lib/occi/api/version.rb +5 -0
- data/lib/occi-api.rb +14 -0
- data/occi-api.gemspec +38 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/creates_a_new_compute_resource.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/creates_a_new_network_resource.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/creates_a_new_storage_resource.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/deletes_a_compute_resource.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/deletes_a_network_resource.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/deletes_a_storage_resource.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/deploys_an_instance_based_on_OVF_OVA_file.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/describes_compute_resources.yml +368 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/describes_network_resources.yml +370 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/describes_storage_resources.yml +430 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/establishes_connection.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/finds_and_describes_scoped_os_tpl_mixin.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/finds_and_describes_scoped_resource_tpl_mixin.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/finds_and_describes_unscoped_mixin.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_compute_resource_using_type_identifier.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_compute_resource_using_type_name.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_network_resource_using_type_identifier.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_network_resource_using_type_name.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_storage_resource_using_type_identifier.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_storage_resource_using_type_name.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_entity_type_identifiers.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_entity_types.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_link_type_identifiers.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_link_types.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_mixin_type_identifiers.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_mixin_types.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_mixins.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_resource_type_identifiers.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_resource_types.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_compute_resources.yml +308 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_network_resources.yml +308 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_os_tpl_mixins.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_resource_tpl_mixins.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_storage_resources.yml +310 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/refreshes_its_model.yml +485 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/triggers_an_action_on_a_compute_resource.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/triggers_an_action_on_a_network_resource.yml +266 -0
- data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/triggers_an_action_on_a_storage_resource.yml +266 -0
- data/spec/occi/api/client/client_amqp_spec.rb +158 -0
- data/spec/occi/api/client/client_http_spec.rb +292 -0
- data/spec/occi/api/client/http/authn_utils_spec.rb +55 -0
- data/spec/occi/api/client/http/httparty_fix_spec.rb +0 -0
- data/spec/occi/api/client/http/net_http_fix_spec.rb +0 -0
- data/spec/occi/api/client/http/rocci-cred-cert.pem +3 -0
- data/spec/occi/api/client/http/rocci-cred-key-jruby.pem +3 -0
- data/spec/occi/api/client/http/rocci-cred-key.pem +3 -0
- data/spec/occi/api/client/http/rocci-cred.p12 +0 -0
- data/spec/occi/api/dsl_spec.rb +21 -0
- data/spec/spec_helper.rb +38 -0
- metadata +379 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
require 'httparty'
|
|
2
|
+
|
|
3
|
+
require 'occi/api/client/http/net_http_fix'
|
|
4
|
+
require 'occi/api/client/http/httparty_fix'
|
|
5
|
+
require 'occi/api/client/http/authn_utils'
|
|
6
|
+
|
|
7
|
+
module Occi
|
|
8
|
+
module Api
|
|
9
|
+
module Client
|
|
10
|
+
|
|
11
|
+
class ClientHttp < ClientBase
|
|
12
|
+
|
|
13
|
+
# HTTParty for raw HTTP requests
|
|
14
|
+
include HTTParty
|
|
15
|
+
|
|
16
|
+
# TODO: uncomment the following line as JSON is properly implemented in OpenStack
|
|
17
|
+
# headers 'Accept' => 'application/occi+json,text/plain;q=0.8,text/occi;q=0.2'
|
|
18
|
+
headers 'Accept' => 'text/plain,text/occi;q=0.2'
|
|
19
|
+
|
|
20
|
+
# hash mapping HTTP response codes to human-readable messages
|
|
21
|
+
HTTP_CODES = {
|
|
22
|
+
"100" => "Continue",
|
|
23
|
+
"101" => "Switching Protocols",
|
|
24
|
+
"200" => "OK",
|
|
25
|
+
"201" => "Created",
|
|
26
|
+
"202" => "Accepted",
|
|
27
|
+
"203" => "Non-Authoritative Information",
|
|
28
|
+
"204" => "No Content",
|
|
29
|
+
"205" => "Reset Content",
|
|
30
|
+
"206" => "Partial Content",
|
|
31
|
+
"300" => "Multiple Choices",
|
|
32
|
+
"301" => "Moved Permanently",
|
|
33
|
+
"302" => "Found",
|
|
34
|
+
"303" => "See Other",
|
|
35
|
+
"304" => "Not Modified",
|
|
36
|
+
"305" => "Use Proxy",
|
|
37
|
+
"307" => "Temporary Redirect",
|
|
38
|
+
"400" => "Bad Request",
|
|
39
|
+
"401" => "Unauthorized",
|
|
40
|
+
"402" => "Payment Required",
|
|
41
|
+
"403" => "Forbidden",
|
|
42
|
+
"404" => "Not Found",
|
|
43
|
+
"405" => "Method Not Allowed",
|
|
44
|
+
"406" => "Not Acceptable",
|
|
45
|
+
"407" => "Proxy Authentication Required",
|
|
46
|
+
"408" => "Request Time-out",
|
|
47
|
+
"409" => "Conflict",
|
|
48
|
+
"410" => "Gone",
|
|
49
|
+
"411" => "Length Required",
|
|
50
|
+
"412" => "Precondition Failed",
|
|
51
|
+
"413" => "Request Entity Too Large",
|
|
52
|
+
"414" => "Request-URI Too Large",
|
|
53
|
+
"415" => "Unsupported Media Type",
|
|
54
|
+
"416" => "Requested range not satisfiable",
|
|
55
|
+
"417" => "Expectation Failed",
|
|
56
|
+
"500" => "Internal Server Error",
|
|
57
|
+
"501" => "Not Implemented",
|
|
58
|
+
"502" => "Bad Gateway",
|
|
59
|
+
"503" => "Service Unavailable",
|
|
60
|
+
"504" => "Gateway Time-out",
|
|
61
|
+
"505" => "HTTP Version not supported"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Initializes client data structures and retrieves OCCI model
|
|
65
|
+
# from the server.
|
|
66
|
+
#
|
|
67
|
+
# @example
|
|
68
|
+
# options = {
|
|
69
|
+
# :endpoint => "http://localhost:3300/",
|
|
70
|
+
# :auth => {:type => "none"},
|
|
71
|
+
# :log => {:out => STDERR, :level => Occi::Log::WARN, :logger => nil},
|
|
72
|
+
# :auto_connect => "value", auto_connect => true,
|
|
73
|
+
# :media_type => nil
|
|
74
|
+
# }
|
|
75
|
+
#
|
|
76
|
+
# Occi::Api::Client::ClientHttp.new options # => #<Occi::Api::Client::ClientHttp>
|
|
77
|
+
#
|
|
78
|
+
# @param [Hash] options, for available options and defaults see examples
|
|
79
|
+
# @return [Occi::Api::Client::ClientHttp] client instance
|
|
80
|
+
def initialize(options = {})
|
|
81
|
+
super options
|
|
82
|
+
|
|
83
|
+
# get model information from the endpoint
|
|
84
|
+
# and create Occi::Model instance
|
|
85
|
+
model = get('/-/')
|
|
86
|
+
set_model model
|
|
87
|
+
|
|
88
|
+
# auto-connect?
|
|
89
|
+
@connected = @options[:auto_connect]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @see Occi::Api::Client::ClientBase
|
|
93
|
+
def list(resource_type_identifier=nil)
|
|
94
|
+
if resource_type_identifier
|
|
95
|
+
# convert type to type identifier
|
|
96
|
+
kinds = @model.kinds.select {
|
|
97
|
+
|kind| kind.term == resource_type_identifier
|
|
98
|
+
}
|
|
99
|
+
if kinds.any?
|
|
100
|
+
resource_type_identifier = kinds.first.type_identifier
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
raise 'Unkown resource type identifier!' unless resource_type_identifier
|
|
104
|
+
unless @model.get_by_id resource_type_identifier
|
|
105
|
+
raise "Resource type identifier not allowed with this model! [#{resource_type_identifier}]"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# split the type identifier and get the most important part
|
|
109
|
+
uri_part = resource_type_identifier.split('#').last
|
|
110
|
+
|
|
111
|
+
# request uri-list from the server
|
|
112
|
+
path = uri_part + '/'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
path = '/' unless path
|
|
116
|
+
|
|
117
|
+
headers = self.class.headers.clone
|
|
118
|
+
headers['Accept'] = 'text/uri-list'
|
|
119
|
+
|
|
120
|
+
response = self.class.get(
|
|
121
|
+
@endpoint + path,
|
|
122
|
+
:headers => headers
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# TODO: remove the gsub OCCI-OS hack as soon as they stop using 'uri:'
|
|
126
|
+
response.body.gsub(/\# uri:\/(compute|storage|network)\/[\n]?/, '').split("\n").compact
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @see Occi::Api::Client::ClientBase
|
|
130
|
+
def describe(resource_type_identifier=nil)
|
|
131
|
+
|
|
132
|
+
# convert type to type identifier whenever possible
|
|
133
|
+
if resource_type_identifier
|
|
134
|
+
kinds = @model.kinds.select {
|
|
135
|
+
|kind| kind.term == resource_type_identifier
|
|
136
|
+
}
|
|
137
|
+
if kinds.any?
|
|
138
|
+
resource_type_identifier = kinds.first.type_identifier
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
descriptions = []
|
|
143
|
+
|
|
144
|
+
if resource_type_identifier.nil?
|
|
145
|
+
# no filters, describe all available resources
|
|
146
|
+
descriptions << get('/')
|
|
147
|
+
elsif @model.get_by_id(resource_type_identifier)
|
|
148
|
+
# we got type identifier
|
|
149
|
+
# get all available resources of this type
|
|
150
|
+
locations = list(resource_type_identifier)
|
|
151
|
+
|
|
152
|
+
# make the requests
|
|
153
|
+
locations.each do |location|
|
|
154
|
+
descriptions << get(sanitize_resource_link(location))
|
|
155
|
+
end
|
|
156
|
+
elsif resource_type_identifier.start_with?(@endpoint) || resource_type_identifier.start_with?('/')
|
|
157
|
+
# this is a link of a specific resource (obsolute or relative)
|
|
158
|
+
descriptions << get(sanitize_resource_link(resource_type_identifier))
|
|
159
|
+
else
|
|
160
|
+
raise "Unkown resource type identifier! [#{resource_type_identifier}]"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
descriptions
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @see Occi::Api::Client::ClientBase
|
|
167
|
+
def create(entity)
|
|
168
|
+
|
|
169
|
+
raise "#{entity} not an entity!" unless entity.kind_of? Occi::Core::Entity
|
|
170
|
+
|
|
171
|
+
# is this entity valid?
|
|
172
|
+
entity.model = @model
|
|
173
|
+
entity.check
|
|
174
|
+
|
|
175
|
+
Occi::Log.debug "Entity kind: #{entity.kind}"
|
|
176
|
+
kind = entity.kind
|
|
177
|
+
raise "No kind found for #{entity}" unless kind
|
|
178
|
+
|
|
179
|
+
# get location for this kind of entity
|
|
180
|
+
Occi::Log.debug "Kind location: #{entity.kind.location}"
|
|
181
|
+
location = kind.location
|
|
182
|
+
collection = Occi::Collection.new
|
|
183
|
+
|
|
184
|
+
# is this entity a Resource or a Link?
|
|
185
|
+
Occi::Log.debug "Entity class: #{entity.class.name}"
|
|
186
|
+
collection.resources << entity if entity.kind_of? Occi::Core::Resource
|
|
187
|
+
collection.links << entity if entity.kind_of? Occi::Core::Link
|
|
188
|
+
|
|
189
|
+
# make the request
|
|
190
|
+
post location, collection
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# @see Occi::Api::Client::ClientBase
|
|
194
|
+
def deploy(location)
|
|
195
|
+
raise "File #{location} does not exist!" unless File.exist? location
|
|
196
|
+
|
|
197
|
+
file = File.read(location)
|
|
198
|
+
|
|
199
|
+
if location.include? '.ovf'
|
|
200
|
+
deploy_ovf file
|
|
201
|
+
elsif location.include? '.ova'
|
|
202
|
+
deploy_ova file
|
|
203
|
+
else
|
|
204
|
+
raise "Unsupported descriptor format! Only OVF or OVA files are supported."
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# @see Occi::Api::Client::ClientBase
|
|
209
|
+
def deploy_ovf(descriptor)
|
|
210
|
+
media_types = self.class.head(@endpoint).headers['accept'].to_s
|
|
211
|
+
|
|
212
|
+
if media_types.include? 'application/ovf'
|
|
213
|
+
headers = self.class.headers.clone
|
|
214
|
+
headers['Content-Type'] = 'application/ovf'
|
|
215
|
+
self.class.post(@endpoint + '/compute/',
|
|
216
|
+
:body => descriptor,
|
|
217
|
+
:headers => headers)
|
|
218
|
+
else
|
|
219
|
+
raise "Unsupported descriptor format! Server does not support OVF descriptors."
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# @see Occi::Api::Client::ClientBase
|
|
224
|
+
def deploy_ova(descriptor)
|
|
225
|
+
media_types = self.class.head(@endpoint).headers['accept'].to_s
|
|
226
|
+
|
|
227
|
+
if media_types.include? ' application/ova '
|
|
228
|
+
headers = self.class.headers.clone
|
|
229
|
+
headers['Content-Type'] = 'application/ova'
|
|
230
|
+
self.class.post(@endpoint + '/compute/',
|
|
231
|
+
:body => descriptor,
|
|
232
|
+
:headers => headers)
|
|
233
|
+
else
|
|
234
|
+
raise "Unsupported descriptor format! Server does not support OVA descriptors."
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# @see Occi::Api::Client::ClientBase
|
|
239
|
+
def delete(resource_type_identifier)
|
|
240
|
+
raise 'Resource not provided!' unless resource_type_identifier
|
|
241
|
+
path = path_for_resource_type(resource_type_identifier)
|
|
242
|
+
|
|
243
|
+
Occi::Log.debug("Deleting #{path} ...")
|
|
244
|
+
del path
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# @see Occi::Api::Client::ClientBase
|
|
248
|
+
def trigger(resource_type_identifier, action)
|
|
249
|
+
# TODO: not tested
|
|
250
|
+
raise 'Resource not provided!' unless resource_type_identifier
|
|
251
|
+
type_identifiers = @model.kinds.select {
|
|
252
|
+
|kind| kind.term == resource_type_identifier
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if type_identifiers.any?
|
|
256
|
+
type_identifier = @model.kinds.select {
|
|
257
|
+
|kind| kind.term == resource_type_identifier
|
|
258
|
+
}.first.type_identifier
|
|
259
|
+
|
|
260
|
+
location = @model.get_by_id(type_identifier).location
|
|
261
|
+
resource_type_identifier = @endpoint + location
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
raise "Unknown resource identifier! #{resource_type_identifier}" unless resource_type_identifier.start_with? @endpoint
|
|
265
|
+
|
|
266
|
+
# encapsulate the acion in a collection
|
|
267
|
+
collection = Occi::Collection.new
|
|
268
|
+
scheme, term = action.split(' #')
|
|
269
|
+
collection.actions << Occi::Core::Action.new(scheme + '#', term)
|
|
270
|
+
|
|
271
|
+
# make the request
|
|
272
|
+
path = sanitize_resource_link(resource_type_identifier) + '?action=' + term
|
|
273
|
+
post path, collection
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# @see Occi::Api::Client::ClientBase
|
|
277
|
+
def refresh
|
|
278
|
+
# re-download the model from the server
|
|
279
|
+
model = get('/-/')
|
|
280
|
+
set_model model
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
private
|
|
284
|
+
|
|
285
|
+
# Performs GET request and parses the responses to collections.
|
|
286
|
+
#
|
|
287
|
+
# @example
|
|
288
|
+
# get "/-/" # => #<Occi::Collection>
|
|
289
|
+
# get "/compute/" # => #<Occi::Collection>
|
|
290
|
+
# get "/compute/fs65g4fs6g-sf54g54gsf-aa12faddf52" # => #<Occi::Collection>
|
|
291
|
+
#
|
|
292
|
+
# @param [String] path for the GET request
|
|
293
|
+
# @param [Occi::Collection] collection of filters
|
|
294
|
+
# @return [Occi::Collection] parsed result of the request
|
|
295
|
+
def get(path='', filter=nil)
|
|
296
|
+
# remove the leading slash
|
|
297
|
+
path = path.gsub(/\A\//, '')
|
|
298
|
+
|
|
299
|
+
response = if filter
|
|
300
|
+
categories = filter.categories.collect { |category| category.to_text }.join(',')
|
|
301
|
+
attributes = filter.entities.collect { |entity| entity.attributes.combine.collect { |k, v| k + '=' + v } }.join(',')
|
|
302
|
+
|
|
303
|
+
headers = self.class.headers.clone
|
|
304
|
+
headers['Content-Type'] = 'text/occi'
|
|
305
|
+
headers['Category'] = categories unless categories.empty?
|
|
306
|
+
headers['X-OCCI-Attributes'] = attributes unless attributes.empty?
|
|
307
|
+
|
|
308
|
+
self.class.get(@endpoint + path, :headers => headers)
|
|
309
|
+
else
|
|
310
|
+
self.class.get(@endpoint + path)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
response_msg = response_message response
|
|
314
|
+
raise "HTTP GET failed! #{response_msg}" unless response.code.between? 200, 300
|
|
315
|
+
|
|
316
|
+
Occi::Log.debug "Response location: #{('/' + path).match(/\/.*\//).to_s}"
|
|
317
|
+
kind = @model.get_by_location(('/' + path).match(/\/.*\//).to_s) if @model
|
|
318
|
+
|
|
319
|
+
Occi::Log.debug "Response kind: #{kind}"
|
|
320
|
+
|
|
321
|
+
if kind
|
|
322
|
+
kind.related_to? Occi::Core::Resource ? entity_type = Occi::Core::Resource : entity_type = nil
|
|
323
|
+
entity_type = Occi::Core::Link if kind.related_to? Occi::Core::Link
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
Occi::Log.debug "Parser call: #{response.content_type} #{entity_type} #{path.include?('-/')}"
|
|
327
|
+
collection = Occi::Parser.parse(response.content_type, response.body, path.include?('-/'), entity_type, response.headers)
|
|
328
|
+
|
|
329
|
+
Occi::Log.debug "Parsed collection: empty? #{collection.empty?}"
|
|
330
|
+
collection
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Performs POST requests and returns URI locations. Resource data must be provided
|
|
334
|
+
# in an Occi::Collection instance.
|
|
335
|
+
#
|
|
336
|
+
# @example
|
|
337
|
+
# collection = Occi::Collection.new
|
|
338
|
+
# collection.resources << entity if entity.kind_of? Occi::Core::Resource
|
|
339
|
+
# collection.links << entity if entity.kind_of? Occi::Core::Link
|
|
340
|
+
#
|
|
341
|
+
# post "/compute/", collection # => "http://localhost:3300/compute/23sf4g65as-asdgsg2-sdfgsf2g"
|
|
342
|
+
# post "/network/", collection # => "http://localhost:3300/network/23sf4g65as-asdgsg2-sdfgsf2g"
|
|
343
|
+
# post "/storage/", collection # => "http://localhost:3300/storage/23sf4g65as-asdgsg2-sdfgsf2g"
|
|
344
|
+
#
|
|
345
|
+
# @param [String] path for the POST request
|
|
346
|
+
# @param [Occi::Collection] resource data to be POSTed
|
|
347
|
+
# @return [String] URI location
|
|
348
|
+
def post(path, collection)
|
|
349
|
+
# remove the leading slash
|
|
350
|
+
path = path.gsub(/\A\//, '')
|
|
351
|
+
|
|
352
|
+
headers = self.class.headers.clone
|
|
353
|
+
headers['Content-Type'] = @media_type
|
|
354
|
+
|
|
355
|
+
response = case @media_type
|
|
356
|
+
when 'application/occi+json'
|
|
357
|
+
self.class.post(@endpoint + path,
|
|
358
|
+
:body => collection.to_json,
|
|
359
|
+
:headers => headers)
|
|
360
|
+
when 'text/occi'
|
|
361
|
+
self.class.post(@endpoint + path,
|
|
362
|
+
:headers => collection.to_header.merge(headers))
|
|
363
|
+
else
|
|
364
|
+
self.class.post(@endpoint + path,
|
|
365
|
+
:body => collection.to_text,
|
|
366
|
+
:headers => headers)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
response_msg = response_message response
|
|
370
|
+
|
|
371
|
+
case response.code
|
|
372
|
+
when 200
|
|
373
|
+
collection = Occi::Parser.parse(response.header["content-type"].split(";").first, response.body)
|
|
374
|
+
|
|
375
|
+
if collection.empty?
|
|
376
|
+
Occi::Parser.locations(response.header["content-type"].split(";").first, response.body, response.headers).first
|
|
377
|
+
else
|
|
378
|
+
collection.resources.first.location if collection.resources.first
|
|
379
|
+
end
|
|
380
|
+
when 201
|
|
381
|
+
Occi::Parser.locations(response.header["content-type"].split(";").first, response.body, response.headers).first
|
|
382
|
+
else
|
|
383
|
+
raise "HTTP POST failed! #{response_msg}"
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Performs PUT requests and parses responses to collections.
|
|
388
|
+
#
|
|
389
|
+
# @example
|
|
390
|
+
# TODO: add examples
|
|
391
|
+
#
|
|
392
|
+
# @param [String] path for the PUT request
|
|
393
|
+
# @param [Occi::Collection] resource data to send
|
|
394
|
+
# @return [Occi::Collection] parsed result of the request
|
|
395
|
+
def put(path, collection)
|
|
396
|
+
# remove the leading slash
|
|
397
|
+
path = path.gsub(/\A\//, '')
|
|
398
|
+
|
|
399
|
+
headers = self.class.headers.clone
|
|
400
|
+
headers['Content-Type'] = @media_type
|
|
401
|
+
|
|
402
|
+
response = case @media_type
|
|
403
|
+
when 'application/occi+json'
|
|
404
|
+
self.class.post(@endpoint + path,
|
|
405
|
+
:body => collection.to_json,
|
|
406
|
+
:headers => headers)
|
|
407
|
+
when 'text/occi'
|
|
408
|
+
self.class.post(@endpoint + path,
|
|
409
|
+
:headers => collection.to_header.merge(headers))
|
|
410
|
+
else
|
|
411
|
+
self.class.post(@endpoint + path,
|
|
412
|
+
:body => collection.to_text,
|
|
413
|
+
:headers => headers)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
response_msg = response_message response
|
|
417
|
+
|
|
418
|
+
case response.code
|
|
419
|
+
when 200, 201
|
|
420
|
+
Occi::Parser.parse(response.header["content-type"].split(";").first, response.body)
|
|
421
|
+
else
|
|
422
|
+
raise "HTTP POST failed! #{response_msg}"
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Performs DELETE requests and returns True on success.
|
|
427
|
+
#
|
|
428
|
+
# @example
|
|
429
|
+
# del "/compute/65sf4g65sf4g-sf6g54sf5g-sfgsf32g3" # => true
|
|
430
|
+
#
|
|
431
|
+
# @param [String] path for the DELETE request
|
|
432
|
+
# @param [Occi::Collection] collection of filters (currently NOT used)
|
|
433
|
+
# @return [Boolean] status
|
|
434
|
+
def del(path, filter=nil)
|
|
435
|
+
# remove the leading slash
|
|
436
|
+
path = path.gsub(/\A\//, '')
|
|
437
|
+
|
|
438
|
+
response = self.class.delete(@endpoint + path)
|
|
439
|
+
|
|
440
|
+
response_msg = response_message response
|
|
441
|
+
raise "HTTP DELETE failed! #{response_msg}" unless response.code.between? 200, 300
|
|
442
|
+
|
|
443
|
+
true
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# @see Occi::Api::Client::ClientBase
|
|
447
|
+
def set_logger(log_options)
|
|
448
|
+
super log_options
|
|
449
|
+
|
|
450
|
+
self.class.debug_output $stderr if log_options[:level] == Occi::Log::DEBUG
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# @see Occi::Api::Client::ClientBase
|
|
454
|
+
def set_auth(auth_options, fallback = false)
|
|
455
|
+
@auth_options = auth_options
|
|
456
|
+
|
|
457
|
+
case @auth_options[:type]
|
|
458
|
+
when "basic"
|
|
459
|
+
@authn_plugin = Http::AuthnPlugins::Basic.new self, @auth_options
|
|
460
|
+
when "digest"
|
|
461
|
+
@authn_plugin = Http::AuthnPlugins::Digest.new self, @auth_options
|
|
462
|
+
when "x509"
|
|
463
|
+
@authn_plugin = Http::AuthnPlugins::X509.new self, @auth_options
|
|
464
|
+
when "keystone"
|
|
465
|
+
raise ::Occi::Api::Client::Errors::AuthnError, "This authN method is for fallback only!" unless fallback
|
|
466
|
+
@authn_plugin = Http::AuthnPlugins::Keystone.new self, @auth_options
|
|
467
|
+
when "none", nil
|
|
468
|
+
@authn_plugin = Http::AuthnPlugins::Dummy.new self
|
|
469
|
+
else
|
|
470
|
+
raise ::Occi::Api::Client::Errors::AuthnError, "Unknown authN method [#{@auth_options[:type]}]!"
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
@authn_plugin.setup
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# @see Occi::Api::Client::ClientBase
|
|
477
|
+
def preauthenticate
|
|
478
|
+
begin
|
|
479
|
+
@authn_plugin.authenticate
|
|
480
|
+
rescue ::Occi::Api::Client::Errors::AuthnError => e
|
|
481
|
+
Occi::Log.debug e.message
|
|
482
|
+
|
|
483
|
+
if @authn_plugin.fallbacks.any?
|
|
484
|
+
@auth_options[:original_type] = @auth_options[:type]
|
|
485
|
+
@auth_options[:type] = @authn_plugin.fallbacks.first
|
|
486
|
+
|
|
487
|
+
set_auth @auth_options, true
|
|
488
|
+
@authn_plugin.authenticate
|
|
489
|
+
else
|
|
490
|
+
raise e
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# @see Occi::Api::Client::ClientBase
|
|
496
|
+
def set_media_type(force_type = nil)
|
|
497
|
+
# force media_type if provided
|
|
498
|
+
if force_type
|
|
499
|
+
self.class.headers 'Accept' => force_type
|
|
500
|
+
@media_type = force_type
|
|
501
|
+
else
|
|
502
|
+
media_types = self.class.head(@endpoint).headers['accept']
|
|
503
|
+
Occi::Log.debug("Available media types: #{media_types}")
|
|
504
|
+
@media_type = case media_types
|
|
505
|
+
when /application\/occi\+json/
|
|
506
|
+
'application/occi+json'
|
|
507
|
+
else
|
|
508
|
+
'text/plain'
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# Generates a human-readable response message based on the HTTP response code.
|
|
514
|
+
#
|
|
515
|
+
# @example
|
|
516
|
+
# response_message self.class.delete(@endpoint + path)
|
|
517
|
+
# # => 'HTTP Response status: [200] OK'
|
|
518
|
+
#
|
|
519
|
+
# @param [HTTParty::Response] HTTParty response object
|
|
520
|
+
# @return [String] message
|
|
521
|
+
def response_message(response)
|
|
522
|
+
@last_response = response
|
|
523
|
+
'HTTP Response status: [' + response.code.to_s + '] ' + reason_phrase(response.code)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# Converts HTTP response codes to human-readable phrases.
|
|
527
|
+
#
|
|
528
|
+
# @example
|
|
529
|
+
# reason_phrase(500) # => "Internal Server Error"
|
|
530
|
+
#
|
|
531
|
+
# @param [Integer] HTTP response code
|
|
532
|
+
# @return [String] human-readable phrase
|
|
533
|
+
def reason_phrase(code)
|
|
534
|
+
HTTP_CODES[code.to_s]
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'occi/api/client/errors/authn_error'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Occi::Api::Client
|
|
2
|
+
module Http
|
|
3
|
+
module AuthnPlugins
|
|
4
|
+
|
|
5
|
+
class Base
|
|
6
|
+
attr_reader :env_ref
|
|
7
|
+
attr_reader :options
|
|
8
|
+
attr_reader :fallbacks
|
|
9
|
+
|
|
10
|
+
def initialize(env_ref, options = {})
|
|
11
|
+
@options = options
|
|
12
|
+
@env_ref = env_ref
|
|
13
|
+
@fallbacks = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def setup(options = {}); end
|
|
17
|
+
|
|
18
|
+
def authenticate(options = {})
|
|
19
|
+
response = @env_ref.class.head @env_ref.endpoint
|
|
20
|
+
raise ::Occi::Api::Client::Errors::AuthnError, "Authentication failed with code #{response.code.to_s}!" unless response.success?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Occi::Api::Client
|
|
2
|
+
module Http
|
|
3
|
+
module AuthnPlugins
|
|
4
|
+
|
|
5
|
+
class Basic < Base
|
|
6
|
+
|
|
7
|
+
def initialize(env_ref, options = {})
|
|
8
|
+
super env_ref, options
|
|
9
|
+
@fallbacks = %w(keystone)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def setup(options = {})
|
|
13
|
+
# set up basic auth
|
|
14
|
+
raise ArgumentError, "Missing required options 'username' and 'password' for basic auth!" unless @options[:username] && @options[:password]
|
|
15
|
+
@env_ref.class.basic_auth @options[:username], @options[:password]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Occi::Api::Client
|
|
2
|
+
module Http
|
|
3
|
+
module AuthnPlugins
|
|
4
|
+
|
|
5
|
+
class Digest < Base
|
|
6
|
+
|
|
7
|
+
def initialize(env_ref, options = {})
|
|
8
|
+
super env_ref, options
|
|
9
|
+
@fallbacks = %w(keystone)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def setup(options = {})
|
|
13
|
+
# set up digest auth
|
|
14
|
+
raise ArgumentError, "Missing required options 'username' and 'password' for digest auth!" unless @options[:username] and @options[:password]
|
|
15
|
+
@env_ref.class.digest_auth @options[:username], @options[:password]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|