visor-image 0.0.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.
Files changed (43) hide show
  1. data/bin/visor +423 -0
  2. data/bin/visor-image +10 -0
  3. data/config/server.rb +14 -0
  4. data/lib/image/auth.rb +147 -0
  5. data/lib/image/cli.rb +397 -0
  6. data/lib/image/client.rb +490 -0
  7. data/lib/image/meta.rb +219 -0
  8. data/lib/image/routes/delete_all_images.rb +40 -0
  9. data/lib/image/routes/delete_image.rb +62 -0
  10. data/lib/image/routes/get_image.rb +78 -0
  11. data/lib/image/routes/get_images.rb +54 -0
  12. data/lib/image/routes/get_images_detail.rb +54 -0
  13. data/lib/image/routes/head_image.rb +51 -0
  14. data/lib/image/routes/post_image.rb +189 -0
  15. data/lib/image/routes/put_image.rb +205 -0
  16. data/lib/image/server.rb +307 -0
  17. data/lib/image/store/cumulus.rb +126 -0
  18. data/lib/image/store/file_system.rb +119 -0
  19. data/lib/image/store/hdfs.rb +149 -0
  20. data/lib/image/store/http.rb +78 -0
  21. data/lib/image/store/lunacloud.rb +126 -0
  22. data/lib/image/store/s3.rb +121 -0
  23. data/lib/image/store/store.rb +39 -0
  24. data/lib/image/store/walrus.rb +130 -0
  25. data/lib/image/version.rb +5 -0
  26. data/lib/visor-image.rb +30 -0
  27. data/spec/lib/client_spec.rb +0 -0
  28. data/spec/lib/meta_spec.rb +230 -0
  29. data/spec/lib/routes/delete_image_spec.rb +98 -0
  30. data/spec/lib/routes/get_image_spec.rb +78 -0
  31. data/spec/lib/routes/get_images_detail_spec.rb +104 -0
  32. data/spec/lib/routes/get_images_spec.rb +104 -0
  33. data/spec/lib/routes/head_image_spec.rb +51 -0
  34. data/spec/lib/routes/post_image_spec.rb +112 -0
  35. data/spec/lib/routes/put_image_spec.rb +109 -0
  36. data/spec/lib/server_spec.rb +62 -0
  37. data/spec/lib/store/cumulus_spec.rb +0 -0
  38. data/spec/lib/store/file_system_spec.rb +32 -0
  39. data/spec/lib/store/http_spec.rb +56 -0
  40. data/spec/lib/store/s3_spec.rb +37 -0
  41. data/spec/lib/store/store_spec.rb +36 -0
  42. data/spec/lib/store/walrus_spec.rb +0 -0
  43. metadata +217 -0
data/lib/image/meta.rb ADDED
@@ -0,0 +1,219 @@
1
+ require 'em-synchrony'
2
+ require 'em-synchrony/em-http'
3
+ require 'json'
4
+
5
+ module Visor
6
+ module Image
7
+
8
+ # The API for the VISoR Meta Server. This class supports all image metadata manipulation operations.
9
+ #
10
+ # This is the entry-point for the VISoR Image Server to communicate with the VISoR Meta Server,
11
+ # here are processed and logged all the calls to the meta server coming from it.
12
+ #
13
+ class Meta
14
+ include Visor::Common::Exception
15
+
16
+ DEFAULT_HOST = '0.0.0.0'
17
+ DEFAULT_PORT = 4567
18
+
19
+ attr_reader :host, :port, :ssl
20
+
21
+ # Initializes a new new VISoR Meta Image.
22
+ #
23
+ # @option opts [String] :host (DEFAULT_HOST) The host address where VISoR meta server resides.
24
+ # @option opts [String] :port (DEFAULT_PORT) The host port where VISoR meta server resides.
25
+ # @option opts [String] :ssl (false) If the connection should be made through HTTPS (SSL).
26
+ #
27
+ def initialize(opts = {})
28
+ @host = opts[:host] || DEFAULT_HOST
29
+ @port = opts[:port] || DEFAULT_PORT
30
+ @ssl = opts[:ssl] || false
31
+ end
32
+
33
+ # Retrieves brief metadata of all public images.
34
+ # Options for filtering the returned results can be passed in.
35
+ #
36
+ # @option query [String] :<attribute_name> The image attribute value to filter returned results.
37
+ # @param owner (nil) [String] The user's access key to look for its private images too.
38
+ # @option query [String] :sort ("_id") The image attribute to sort returned results.
39
+ # @option query [String] :dir ("asc") The direction to sort results ("asc"/"desc").
40
+ #
41
+ # @return [Array] All public images brief metadata.
42
+ # Just {Visor::Meta::Backends::Base::BRIEF BRIEF} fields are returned.
43
+ #
44
+ # @raise [NotFound] If there are no public images registered on the server.
45
+ #
46
+ def get_images(query = {}, owner=nil)
47
+ http = request.get path: '/images', query: query.merge({access: 'public'}), head: get_headers
48
+ pub = return_response(http)
49
+ priv = []
50
+ if owner
51
+ http = request.get path: '/images', query: query.merge({access: 'private', owner: owner}), head: get_headers
52
+ begin
53
+ priv = return_response(http)
54
+ rescue => e
55
+ nil
56
+ end
57
+ end
58
+ pub + priv
59
+ end
60
+
61
+ # Retrieves detailed metadata of all public images.
62
+ #
63
+ # Filtering and querying works the same as with {#get_images}. The only difference is the number
64
+ # of disclosed attributes.
65
+ #
66
+ # @option query [String] :attribute_name The image attribute value to filter returned results.
67
+ # @param owner (nil) [String] The user's access_key to look for its private images too.
68
+ # @option query [String] :sort ("_id") The image attribute to sort returned results.
69
+ # @option query [String] :dir ("asc") The direction to sort results ("asc"/"desc").
70
+ #
71
+ # @return [Array] All public images detailed metadata.
72
+ # The {Visor::Meta::Backends::Base::DETAIL_EXC DETAIL_EXC} fields are excluded from results.
73
+ #
74
+ # @raise [NotFound] If there are no public images registered on the server.
75
+ #
76
+ def get_images_detail(query = {}, owner=nil)
77
+ http = request.get path: '/images/detail', query: query, head: get_headers
78
+ pub = return_response(http)
79
+ priv = []
80
+ if owner
81
+ http = request.get path: '/images/detail', query: query.merge({access: 'private', owner: owner}), head: get_headers
82
+ begin
83
+ priv = return_response(http)
84
+ rescue => e
85
+ nil
86
+ end
87
+ end
88
+ pub + priv
89
+ end
90
+
91
+ # Retrieves detailed image metadata of the image with the given id.
92
+ #
93
+ # @param id [String] The wanted image's _id.
94
+ #
95
+ # @return [Hash] The requested image metadata.
96
+ #
97
+ # @raise [NotFound] If image not found.
98
+ #
99
+ def get_image(id)
100
+ http = request.get path: "/images/#{id}", head: get_headers
101
+ return_response(http)
102
+ end
103
+
104
+ # Register a new image on the server with the given metadata and returns its metadata.
105
+ #
106
+ # @param meta [Hash] The image metadata.
107
+ #
108
+ # @return [Hash] The already inserted image metadata.
109
+ #
110
+ # @raise [Invalid] If image meta validation fails.
111
+ #
112
+ def post_image(meta)
113
+ body = prepare_body(meta)
114
+ http = request.post path: '/images', body: body, head: post_headers
115
+ return_response(http)
116
+ end
117
+
118
+ # Updates an image record with the given metadata and returns its metadata.
119
+ #
120
+ # @param id [String] The image's _id which will be updated.
121
+ # @param meta [Hash] The image metadata.
122
+ #
123
+ # @return [Hash] The already updated image metadata.
124
+ #
125
+ # @raise [Invalid] If image meta validation fails.
126
+ # @raise [NotFound] If required image was not found.
127
+ #
128
+ def put_image(id, meta)
129
+ body = prepare_body(meta)
130
+ http = request.put path: "/images/#{id}", body: body, head: put_headers
131
+ return_response(http)
132
+ end
133
+
134
+ # Removes an image record based on its _id and returns its metadata.
135
+ #
136
+ # @param id [String] The image's _id which will be deleted.
137
+ #
138
+ # @return [Hash] The already deleted image metadata. This is useful for recover on accidental delete.
139
+ #
140
+ # @raise [NotFound] If required image was not found.
141
+ #
142
+ def delete_image(id)
143
+ http = request.delete path: "/images/#{id}", head: delete_headers
144
+ return_response(http)
145
+ end
146
+
147
+ private
148
+
149
+ # Generate a valid JSON request body for POST and PUT requests.
150
+ # It generates a JSON object encapsulated inside a :image key and then returns it.
151
+ #
152
+ # @param hash [Hash] The hash with the key/value pairs to generate a JSON object from.
153
+ #
154
+ # @return [Hash] If an :image key is already present in the hash, it just returns the plain
155
+ # JSON object, otherwise, encapsulate the hash inside a :image key and returns it.
156
+ #
157
+ def prepare_body(hash)
158
+ hash.has_key?(:image) ? meta.to_json : {image: hash}.to_json
159
+ end
160
+
161
+ # Process requests by preparing its headers, launch them and assert or raise their response.
162
+ #
163
+ # @param request [EventMachine::HttpRequest] The request which will be launched.
164
+ #
165
+ # @return [String, Hash] If an error is raised, then it parses and returns its message,
166
+ # otherwise it properly parse and return the response body.
167
+ #
168
+ # @raise [NotFound] If required image was not found (on a GET, PUT or DELETE request).
169
+ # @raise [Invalid] If image meta validation fails (on a POST or PUT request).
170
+ #
171
+ def return_response(http)
172
+ body = http.response
173
+ status = http.response_header.status.to_i
174
+ parsed = JSON.parse(body, symbolize_names: true)
175
+
176
+ case status
177
+ when 404 then
178
+ raise NotFound, parsed[:message]
179
+ when 400 then
180
+ raise Invalid, parsed[:message]
181
+ when 500 then
182
+ raise InternalError, parsed[:message]
183
+ else
184
+ parsed[:image] || parsed[:images]
185
+ end
186
+ end
187
+
188
+ # Generate a new HTTP or HTTPS connection based on initialization parameters.
189
+ #
190
+ # @return [EventMachine::HttpRequest] A HTTP or HTTPS (not done yet) connection ready to use.
191
+ #
192
+ def request
193
+ if @ssl
194
+ #TODO: ssl connection
195
+ else
196
+ EventMachine::HttpRequest.new("http://#{@host}:#{@port}")
197
+ end
198
+ end
199
+
200
+ # Fill common header keys before each request. This sets the 'User-Agent' and 'Accept'
201
+ # headers for every request and additionally sets the 'content-type' header
202
+ # for POST and PUT requests.
203
+ #
204
+ def get_headers
205
+ {'User-Agent' => 'VISoR Image Server',
206
+ 'Accept' => 'application/json'}
207
+ end
208
+
209
+ def post_headers
210
+ {'User-Agent' => 'VISoR Image Server',
211
+ 'Accept' => 'application/json',
212
+ 'content-type' => 'application/json'}
213
+ end
214
+
215
+ alias :delete_headers :get_headers
216
+ alias :put_headers :post_headers
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,40 @@
1
+ require 'goliath'
2
+
3
+ module Visor
4
+ module Image
5
+
6
+ # Delete all images metadata
7
+ #
8
+ class DeleteAllImages < Goliath::API
9
+ include Visor::Common::Exception
10
+ use Goliath::Rack::Render, 'json'
11
+
12
+ # Pre-process headers as they arrive and load them into a environment variable.
13
+ #
14
+ # @param [Object] env The Goliath environment variables.
15
+ # @param [Object] headers The incoming request HTTP headers.
16
+ #
17
+ def on_headers(env, headers)
18
+ logger.debug "Received headers: #{headers.inspect}"
19
+ env['headers'] = headers
20
+ end
21
+
22
+ def response(env)
23
+ authorize(env, vas)
24
+ images = vms.get_images
25
+ images.each { |image| vms.delete_image(image[:_id]) }
26
+ [200, {}, nil]
27
+ rescue Forbidden => e
28
+ exit_error(403, e.message)
29
+ rescue NotFound => e
30
+ exit_error(404, e.message)
31
+ end
32
+
33
+ def exit_error(code, message)
34
+ logger.error message
35
+ [code, {}, {code: code, message: message}]
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,62 @@
1
+ require 'goliath'
2
+
3
+ module Visor
4
+ module Image
5
+
6
+ # Delete the metadata and data of the image with the given id.
7
+ #
8
+ class DeleteImage < Goliath::API
9
+ include Visor::Common::Exception
10
+ include Visor::Common::Util
11
+ use Goliath::Rack::Render, ['json', 'xml']
12
+
13
+ # Pre-process headers as they arrive and load them into a environment variable.
14
+ #
15
+ # @param [Object] env The Goliath environment variables.
16
+ # @param [Object] headers The incoming request HTTP headers.
17
+ #
18
+ def on_headers(env, headers)
19
+ logger.debug "Received headers: #{headers.inspect}"
20
+ env['headers'] = headers
21
+ end
22
+
23
+ # Query database to delete the wanted image based on its id.
24
+ #
25
+ # @param [Object] env The Goliath environment variables.
26
+ #
27
+ # @return [Array] The HTTP response containing the image
28
+ # metadata or an error code and its messages if anything was raised.
29
+ #
30
+ def response(env)
31
+ authorize(env, vas)
32
+ meta = vms.delete_image(params[:id])
33
+ uri = meta[:location]
34
+ name = meta[:store]
35
+
36
+ if uri && name
37
+ store = Visor::Image::Store.get_backend(uri, configs)
38
+ store.delete unless name == 'http'
39
+ end
40
+ rescue Forbidden => e
41
+ exit_error(403, e.message)
42
+ rescue NotFound => e
43
+ exit_error(404, e.message)
44
+ else
45
+ [200, {}, {image: meta}]
46
+ end
47
+
48
+ # Produce an HTTP response with an error code and message.
49
+ #
50
+ # @param [Fixnum] code The error code.
51
+ # @param [String] message The error message.
52
+ #
53
+ # @return [Array] The HTTP response containing an error code and its message.
54
+ #
55
+ def exit_error(code, message)
56
+ logger.error message
57
+ [code, {}, {code: code, message: message}]
58
+ end
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,78 @@
1
+ require 'goliath'
2
+
3
+ module Visor
4
+ module Image
5
+
6
+ # Get image data and metadata for the given id.
7
+ #
8
+ class GetImage < Goliath::API
9
+ include Visor::Common::Exception
10
+ include Visor::Common::Util
11
+ use Goliath::Rack::Render, ['json', 'xml']
12
+
13
+ # Pre-process headers as they arrive and load them into a environment variable.
14
+ #
15
+ # @param [Object] env The Goliath environment variables.
16
+ # @param [Object] headers The incoming request HTTP headers.
17
+ #
18
+ def on_headers(env, headers)
19
+ logger.debug "Received headers: #{headers.inspect}"
20
+ env['headers'] = headers
21
+ end
22
+
23
+ # Query database to retrieve the wanted image meta and return it in
24
+ # headers, along with the image file, if any, streaming it in request body.
25
+ #
26
+ # @param [Object] env The Goliath environment variables.
27
+ #
28
+ def response(env)
29
+ begin
30
+ authorize(env, vas)
31
+ meta = vms.get_image(params[:id])
32
+ uri = meta[:location]
33
+ if uri
34
+ store = Visor::Image::Store.get_backend(uri, configs)
35
+ store.file_exists?
36
+ end
37
+ rescue Forbidden => e
38
+ return exit_error(403, e.message)
39
+ rescue NotFound => e
40
+ return exit_error(404, e.message)
41
+ end
42
+
43
+ custom = {'Content-Type' => 'application/octet-stream', 'X-Stream' => 'Goliath'}
44
+ headers = push_meta_into_headers(meta, custom)
45
+
46
+ if uri
47
+ EM.next_tick do
48
+ store.get { |chunk| chunk ? env.chunked_stream_send(chunk) : env.chunked_stream_close }
49
+ end
50
+ chunked_streaming_response(200, headers)
51
+ else
52
+ [200, headers, nil]
53
+ end
54
+ end
55
+
56
+ # On connection close log a message.
57
+ #
58
+ # @param [Object] env The Goliath environment variables.
59
+ #
60
+ def on_close(env)
61
+ logger.info 'Connection closed'
62
+ end
63
+
64
+ # Produce an HTTP response with an error code and message.
65
+ #
66
+ # @param [Fixnum] code The error code.
67
+ # @param [String] message The error message.
68
+ #
69
+ # @return [Array] The HTTP response containing an error code and its message.
70
+ #
71
+ def exit_error(code, message)
72
+ logger.error message
73
+ [code, {}, {code: code, message: message}]
74
+ end
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,54 @@
1
+ require 'goliath'
2
+
3
+ module Visor
4
+ module Image
5
+
6
+ # Get brief information about all public images.
7
+ #
8
+ class GetImages < Goliath::API
9
+ include Visor::Common::Exception
10
+ include Visor::Common::Util
11
+ use Goliath::Rack::Render, ['json', 'xml']
12
+
13
+ # Pre-process headers as they arrive and load them into a environment variable.
14
+ #
15
+ # @param [Object] env The Goliath environment variables.
16
+ # @param [Object] headers The incoming request HTTP headers.
17
+ #
18
+ def on_headers(env, headers)
19
+ logger.debug "Received headers: #{headers.inspect}"
20
+ env['headers'] = headers
21
+ end
22
+
23
+ # Query database to retrieve public images brief meta and return it in request body.
24
+ #
25
+ # @param [Object] env The Goliath environment variables.
26
+ #
27
+ # @return [Array] The HTTP response containing the images
28
+ # metadata or an error code and its message if anything was raised.
29
+ #
30
+ def response(env)
31
+ access_key = authorize(env, vas)
32
+ meta = vms.get_images(params, access_key)
33
+ [200, {}, {images: meta}]
34
+ rescue Forbidden => e
35
+ exit_error(403, e.message)
36
+ rescue NotFound => e
37
+ exit_error(404, e.message)
38
+ end
39
+
40
+ # Produce an HTTP response with an error code and message.
41
+ #
42
+ # @param [Fixnum] code The error code.
43
+ # @param [String] message The error message.
44
+ #
45
+ # @return [Array] The HTTP response containing an error code and its message.
46
+ #
47
+ def exit_error(code, message)
48
+ logger.error message
49
+ [code, {}, {code: code, message: message}]
50
+ end
51
+ end
52
+
53
+ end
54
+ end