occi-api 4.2.0.beta.4 → 4.2.0.beta.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,2 @@
1
- module Occi::Api::Client::Errors; end
2
-
3
1
  # load all
4
2
  Dir[File.join(File.dirname(__FILE__), 'errors', '*.rb')].each { |file| require file.gsub('.rb', '') }
@@ -1,6 +1,3 @@
1
- require 'occi/api/client/http/authn_plugins/base'
2
- require 'occi/api/client/http/authn_plugins/dummy'
3
- require 'occi/api/client/http/authn_plugins/basic'
4
- require 'occi/api/client/http/authn_plugins/digest'
5
- require 'occi/api/client/http/authn_plugins/x509'
6
- require 'occi/api/client/http/authn_plugins/keystone'
1
+ # load all plugins
2
+ require File.join(File.dirname(__FILE__), 'authn_plugins', 'base')
3
+ Dir[File.join(File.dirname(__FILE__), 'authn_plugins', '*.rb')].each { |file| require file.gsub('.rb', '') }
@@ -0,0 +1,64 @@
1
+ module Occi::Api::Client
2
+ module Http
3
+
4
+ module CodeHelpers
5
+
6
+ # hash mapping HTTP response codes to human-readable messages
7
+ HTTP_CODES = {
8
+ "100" => "Continue",
9
+ "101" => "Switching Protocols",
10
+ "200" => "OK",
11
+ "201" => "Created",
12
+ "202" => "Accepted",
13
+ "203" => "Non-Authoritative Information",
14
+ "204" => "No Content",
15
+ "205" => "Reset Content",
16
+ "206" => "Partial Content",
17
+ "300" => "Multiple Choices",
18
+ "301" => "Moved Permanently",
19
+ "302" => "Found",
20
+ "303" => "See Other",
21
+ "304" => "Not Modified",
22
+ "305" => "Use Proxy",
23
+ "307" => "Temporary Redirect",
24
+ "400" => "Bad Request",
25
+ "401" => "Unauthorized",
26
+ "402" => "Payment Required",
27
+ "403" => "Forbidden",
28
+ "404" => "Not Found",
29
+ "405" => "Method Not Allowed",
30
+ "406" => "Not Acceptable",
31
+ "407" => "Proxy Authentication Required",
32
+ "408" => "Request Time-out",
33
+ "409" => "Conflict",
34
+ "410" => "Gone",
35
+ "411" => "Length Required",
36
+ "412" => "Precondition Failed",
37
+ "413" => "Request Entity Too Large",
38
+ "414" => "Request-URI Too Large",
39
+ "415" => "Unsupported Media Type",
40
+ "416" => "Requested range not satisfiable",
41
+ "417" => "Expectation Failed",
42
+ "500" => "Internal Server Error",
43
+ "501" => "Not Implemented",
44
+ "502" => "Bad Gateway",
45
+ "503" => "Service Unavailable",
46
+ "504" => "Gateway Time-out",
47
+ "505" => "HTTP Version not supported"
48
+ }
49
+
50
+ # Converts HTTP response codes to human-readable phrases.
51
+ #
52
+ # @example
53
+ # reason_phrase(500) # => "Internal Server Error"
54
+ #
55
+ # @param [Integer] HTTP response code
56
+ # @return [String] human-readable phrase
57
+ def reason_phrase(code)
58
+ HTTP_CODES[code.to_s]
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,99 @@
1
+ module Occi::Api::Client
2
+ module Http
3
+
4
+ module Helpers
5
+
6
+ # @see Occi::Api::Client::ClientBase
7
+ def get_logger(log_options)
8
+ logger = super(log_options)
9
+ self.class.debug_output $stderr if logger.level == Occi::Log::DEBUG
10
+
11
+ logger
12
+ end
13
+
14
+ # @see Occi::Api::Client::ClientBase
15
+ def get_auth(auth_options, fallback = false)
16
+ # select appropriate authN type
17
+ case auth_options[:type]
18
+ when "basic"
19
+ @authn_plugin = Http::AuthnPlugins::Basic.new self, auth_options
20
+ when "digest"
21
+ @authn_plugin = Http::AuthnPlugins::Digest.new self, auth_options
22
+ when "x509"
23
+ @authn_plugin = Http::AuthnPlugins::X509.new self, auth_options
24
+ when "keystone"
25
+ raise ::Occi::Api::Client::Errors::AuthnError, "This authN method is for fallback only!" unless fallback
26
+ @authn_plugin = Http::AuthnPlugins::Keystone.new self, auth_options
27
+ when "none", nil
28
+ @authn_plugin = Http::AuthnPlugins::Dummy.new self
29
+ else
30
+ raise ::Occi::Api::Client::Errors::AuthnError, "Unknown authN method [#{@auth_options[:type]}]!"
31
+ end
32
+
33
+ @authn_plugin.setup
34
+
35
+ auth_options
36
+ end
37
+
38
+ # @see Occi::Api::Client::ClientBase
39
+ def preauthenticate
40
+ begin
41
+ @authn_plugin.authenticate
42
+ rescue ::Occi::Api::Client::Errors::AuthnError => e
43
+ Occi::Log.debug e.message
44
+
45
+ if @authn_plugin.fallbacks.any?
46
+ # TODO: multiple fallbacks
47
+ @auth_options[:original_type] = @auth_options[:type]
48
+ @auth_options[:type] = @authn_plugin.fallbacks.first
49
+
50
+ @auth_options = get_auth(@auth_options, true)
51
+ @authn_plugin.authenticate
52
+ else
53
+ raise e
54
+ end
55
+ end
56
+ end
57
+
58
+ # @see Occi::Api::Client::ClientBase
59
+ def get_media_type(force_type = nil)
60
+ # force media_type if provided
61
+ unless force_type.blank?
62
+ self.class.headers 'Accept' => force_type
63
+ media_type = force_type
64
+ else
65
+ media_types = self.class.head(@endpoint.to_s).headers['accept']
66
+
67
+ Occi::Log.debug("Available media types: #{media_types.inspect}")
68
+ media_type = case media_types
69
+ when /application\/occi\+json/
70
+ 'application/occi+json'
71
+ when /application\/occi\+xml/
72
+ 'application/occi+xml'
73
+ when /text\/occi/
74
+ 'text/occi'
75
+ else
76
+ 'text/plain'
77
+ end
78
+ end
79
+
80
+ media_type
81
+ end
82
+
83
+ # Generates a human-readable response message based on the HTTP response code.
84
+ #
85
+ # @example
86
+ # response_message self.class.delete(path)
87
+ # # => 'HTTP Response status: [200] OK'
88
+ #
89
+ # @param [HTTParty::Response] HTTParty response object
90
+ # @return [String] message
91
+ def response_message(response)
92
+ @last_response = response
93
+ "HTTP Response status: [#{response.code.to_s}] #{reason_phrase(response.code)}"
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,2 @@
1
+ # load all fixes
2
+ Dir[File.join(File.dirname(__FILE__), 'monkey_patches', '*.rb')].each { |file| require file.gsub('.rb', '') }
@@ -1,3 +1,7 @@
1
+ ##############################################################################
2
+ ## HTTParty hack allowing the use of X.509 proxy certificates.
3
+ ##############################################################################
4
+
1
5
  module HTTParty
2
6
  class ConnectionAdapter
3
7
 
@@ -0,0 +1,173 @@
1
+ module Occi::Api::Client
2
+ module Http
3
+
4
+ module PartyWrappers
5
+
6
+ # Performs GET request and parses the responses to collections.
7
+ #
8
+ # @example
9
+ # get "/-/" # => #<Occi::Collection>
10
+ # get "/compute/" # => #<Occi::Collection>
11
+ # get "/compute/fs65g4fs6g-sf54g54gsf-aa12faddf52" # => #<Occi::Collection>
12
+ #
13
+ # @param [String] path for the GET request
14
+ # @param [Occi::Collection] collection of filters
15
+ # @return [Occi::Collection] parsed result of the request
16
+ def get(path='/', filter=nil)
17
+ # apply filters if present
18
+ response = if filter
19
+ categories = filter.categories.collect { |category| category.to_text }.join(',')
20
+ attributes = filter.entities.collect { |entity| entity.attributes.combine.collect { |k, v| k + '=' + v } }.join(',')
21
+
22
+ headers = self.class.headers.clone
23
+ headers['Content-Type'] = 'text/occi'
24
+ headers['Category'] = categories unless categories.empty?
25
+ headers['X-OCCI-Attributes'] = attributes unless attributes.empty?
26
+
27
+ self.class.get(path, :headers => headers)
28
+ else
29
+ self.class.get(path)
30
+ end
31
+
32
+ response_msg = response_message response
33
+ raise "HTTP GET failed! #{response_msg}" unless response.code.between? 200, 300
34
+
35
+ Occi::Log.debug "Response location: #{path.inspect}"
36
+ kind = @model.get_by_location(path) if @model
37
+
38
+ Occi::Log.debug "Response kind: #{kind.inspect}"
39
+
40
+ entity_type = nil
41
+ if kind && kind.related_to?(Occi::Core::Link)
42
+ entity_type = Occi::Core::Link
43
+ end
44
+
45
+ entity_type = Occi::Core::Resource unless entity_type
46
+
47
+ Occi::Log.debug "Parser call: #{response.content_type} #{path.include?('/-/')} #{entity_type} #{response.headers.inspect}"
48
+ collection = Occi::Parser.parse(response.content_type, response.body, path.include?('/-/'), entity_type, response.headers)
49
+
50
+ Occi::Log.debug "Parsed collection: empty? #{collection.empty?}"
51
+ collection
52
+ end
53
+
54
+ # Performs POST requests and returns URI locations. Resource data must be provided
55
+ # in an Occi::Collection instance.
56
+ #
57
+ # @example
58
+ # collection = Occi::Collection.new
59
+ # collection.resources << entity if entity.kind_of? Occi::Core::Resource
60
+ # collection.links << entity if entity.kind_of? Occi::Core::Link
61
+ #
62
+ # post "/compute/", collection # => "http://localhost:3300/compute/23sf4g65as-asdgsg2-sdfgsf2g"
63
+ # post "/network/", collection # => "http://localhost:3300/network/23sf4g65as-asdgsg2-sdfgsf2g"
64
+ # post "/storage/", collection # => "http://localhost:3300/storage/23sf4g65as-asdgsg2-sdfgsf2g"
65
+ #
66
+ # @param [String] path for the POST request
67
+ # @param [Occi::Collection] resource data to be POSTed
68
+ # @return [String] URI location
69
+ def post(path, collection)
70
+ raise ArgumentError, "Path is a required argument!" if path.blank?
71
+
72
+ headers = self.class.headers.clone
73
+ headers['Content-Type'] = @media_type
74
+
75
+ response = case @media_type
76
+ when 'application/occi+json'
77
+ self.class.post(path,
78
+ :body => collection.to_json,
79
+ :headers => headers)
80
+ when 'text/occi'
81
+ self.class.post(path,
82
+ :headers => collection.to_header.merge(headers))
83
+ else
84
+ self.class.post(path,
85
+ :body => collection.to_text,
86
+ :headers => headers)
87
+ end
88
+
89
+ response_msg = response_message(response)
90
+
91
+ case response.code
92
+ when 200
93
+ collection = Occi::Parser.parse(response.header["content-type"].split(";").first, response.body)
94
+
95
+ if collection.empty?
96
+ Occi::Parser.locations(response.header["content-type"].split(";").first, response.body, response.headers).first
97
+ else
98
+ collection.resources.first.location if collection.resources.first
99
+ end
100
+ when 201
101
+ # TODO: OCCI-OS hack, look for header Location instead of uri-list
102
+ # This should be probably implemented in Occi::Parser.locations
103
+ if response.header['location']
104
+ response.header['location']
105
+ else
106
+ Occi::Parser.locations(response.header["content-type"].split(";").first, response.body, response.headers).first
107
+ end
108
+ else
109
+ raise "HTTP POST failed! #{response_msg}"
110
+ end
111
+ end
112
+
113
+ # Performs PUT requests and parses responses to collections.
114
+ #
115
+ # @example
116
+ # TODO: add examples
117
+ #
118
+ # @param [String] path for the PUT request
119
+ # @param [Occi::Collection] resource data to send
120
+ # @return [Occi::Collection] parsed result of the request
121
+ def put(path, collection)
122
+ raise ArgumentError, "Path is a required argument!" if path.blank?
123
+
124
+ headers = self.class.headers.clone
125
+ headers['Content-Type'] = @media_type
126
+
127
+ response = case @media_type
128
+ when 'application/occi+json'
129
+ self.class.post(path,
130
+ :body => collection.to_json,
131
+ :headers => headers)
132
+ when 'text/occi'
133
+ self.class.post(path,
134
+ :headers => collection.to_header.merge(headers))
135
+ else
136
+ self.class.post(path,
137
+ :body => collection.to_text,
138
+ :headers => headers)
139
+ end
140
+
141
+ response_msg = response_message(response)
142
+
143
+ case response.code
144
+ when 200, 201
145
+ Occi::Parser.parse(response.header["content-type"].split(";").first, response.body)
146
+ else
147
+ raise "HTTP POST failed! #{response_msg}"
148
+ end
149
+ end
150
+
151
+ # Performs DELETE requests and returns True on success.
152
+ #
153
+ # @example
154
+ # del "/compute/65sf4g65sf4g-sf6g54sf5g-sfgsf32g3" # => true
155
+ #
156
+ # @param [String] path for the DELETE request
157
+ # @param [Occi::Collection] collection of filters (currently NOT used)
158
+ # @return [Boolean] status
159
+ def del(path, filter=nil)
160
+ raise ArgumentError, "Path is a required argument!" if path.blank?
161
+
162
+ response = self.class.delete(path)
163
+
164
+ response_msg = response_message(response)
165
+ raise "HTTP DELETE failed! #{response_msg}" unless response.code.between? 200, 300
166
+
167
+ true
168
+ end
169
+
170
+ end
171
+
172
+ end
173
+ end
data/lib/occi/api/dsl.rb CHANGED
@@ -1,207 +1,46 @@
1
- module Occi
2
- module Api
3
- module Dsl
1
+ # load all parts of the DSL
2
+ Dir[File.join(File.dirname(__FILE__), 'dsl', '*.rb')].each { |file| require file.gsub('.rb', '') }
4
3
 
5
- def connect(protocol, *args)
4
+ module Occi::Api::Dsl
6
5
 
7
- case protocol
8
- when :http
9
- @client = Occi::Api::Client::ClientHttp.new(*args)
10
- else
11
- raise "Protocol #{protocol.to_s} is not supported!"
12
- end
6
+ def connect(protocol = :http, options = {})
7
+ raise ArgumentError, 'Protocol is a required argument!' unless protocol
13
8
 
14
- true
15
- end
16
-
17
- def list(*args)
18
- check
19
- @client.list(*args)
20
- end
21
-
22
- def describe(*args)
23
- check
24
- @client.describe(*args)
25
- end
26
-
27
- def create(*args)
28
- check
29
- @client.create(*args)
30
- end
31
-
32
- def delete(*args)
33
- check
34
- @client.delete(*args)
35
- end
36
-
37
- def trigger(*args)
38
- check
39
- @client.trigger(*args)
40
- end
41
-
42
- def refresh
43
- check
44
- @client.refresh
45
- end
46
-
47
- def model
48
- check
49
- @client.model
50
- end
51
-
52
- ###
53
-
54
- def kind_types
55
- check
56
- @client.get_kind_types
57
- end
58
-
59
- def kind_type_identifier(*args)
60
- check
61
- @client.get_kind_type_identifier(*args)
62
- end
63
-
64
- def kind_type_identifiers
65
- check
66
- @client.get_kind_type_identifiers
67
- end
68
-
69
- def kind_type_identifiers_related_to(*args)
70
- check
71
- @client.get_kind_type_identifiers_related_to(*args)
72
- end
73
-
74
- def category_types
75
- check
76
- @client.get_category_types
77
- end
78
-
79
- def category_type_identifier(*args)
80
- check
81
- @client.get_category_type_identifier(*args)
82
- end
83
-
84
- def category_type_identifiers
85
- check
86
- @client.get_category_type_identifiers
87
- end
88
-
89
- def resource_types
90
- check
91
- @client.get_resource_types
92
- end
93
-
94
- def resource_type_identifier(*args)
95
- check
96
- @client.get_resource_type_identifier(*args)
97
- end
98
-
99
- def resource_type_identifiers
100
- check
101
- @client.get_resource_type_identifiers
102
- end
103
-
104
- def mixin_types
105
- check
106
- @client.get_mixin_types
107
- end
108
-
109
- def mixin_type_identifier(*args)
110
- check
111
- @client.get_mixin_type_identifier(*args)
112
- end
113
-
114
- def mixin_type_identifiers
115
- check
116
- @client.get_mixin_type_identifiers
117
- end
118
-
119
- def entity_types
120
- check
121
- @client.get_entity_types
122
- end
123
-
124
- def entity_type_identifier(*args)
125
- check
126
- @client.get_entity_type_identifier(*args)
127
- end
128
-
129
- def entity_type_identifiers
130
- check
131
- @client.get_entity_type_identifiers
132
- end
133
-
134
- def link_types
135
- check
136
- @client.get_link_types
137
- end
138
-
139
- def link_type_identifier(*args)
140
- check
141
- @client.get_link_type_identifier(*args)
142
- end
143
-
144
- def link_type_identifiers
145
- check
146
- @client.get_link_type_identifiers
147
- end
148
-
149
- ###
150
-
151
- def mixins(*args)
152
- check
153
- @client.get_mixins(*args)
154
- end
155
-
156
- def os_templates
157
- check
158
- @client.get_os_templates
159
- end
160
-
161
- def resource_templates
162
- check
163
- @client.get_resource_templates
164
- end
165
-
166
- def mixin_list(*args)
167
- check
168
- @client.list_mixins(*args)
169
- end
9
+ if block_given?
10
+ options = options.marshal_dump if options.is_a?(OpenStruct)
11
+ options = Hashie::Mash.new(options)
12
+ yield(options)
13
+ end
170
14
 
171
- def resource(*args)
172
- check
173
- @client.get_resource(*args)
174
- end
15
+ case protocol.to_sym
16
+ when :http
17
+ @client = Occi::Api::Client::ClientHttp.new(options)
18
+ else
19
+ raise "Protocol #{protocol.to_s} is not supported!"
20
+ end
175
21
 
176
- def mixin(*args)
177
- check
178
- @client.get_mixin(*args)
179
- end
22
+ @client.connect unless @client.connected
180
23
 
181
- ###
24
+ true
25
+ end
182
26
 
183
- def path_for_kind_type_identifier(*args)
184
- check
185
- @client.path_for_kind_type_identifier(*args)
186
- end
27
+ # include main request methods
28
+ include Occi::Api::Dsl::MainMethods
187
29
 
188
- def path_for_instance(*args)
189
- check
190
- @client.path_for_instance(*args)
191
- end
30
+ # include methods converting between types and type identifiers
31
+ include Occi::Api::Dsl::TypeMethods
192
32
 
193
- def sanitize_instance_link(*args)
194
- check
195
- @client.sanitize_instance_link(*args)
196
- end
33
+ # include methods helping with mixins
34
+ include Occi::Api::Dsl::MixinMethods
197
35
 
198
- private
36
+ # include general helpers, normalizers etc.
37
+ include Occi::Api::Dsl::HelperMethods
199
38
 
200
- def check
201
- raise "You have to issue 'connect' first!" if @client.nil?
202
- raise "Client is disconnected!" unless @client.connected
203
- end
39
+ private
204
40
 
205
- end
41
+ def check
42
+ raise "You have to issue 'connect' first!" unless @client
43
+ raise "Client is disconnected!" unless @client.connected
206
44
  end
45
+
207
46
  end