visor-image 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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