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.
- data/bin/visor +423 -0
- data/bin/visor-image +10 -0
- data/config/server.rb +14 -0
- data/lib/image/auth.rb +147 -0
- data/lib/image/cli.rb +397 -0
- data/lib/image/client.rb +490 -0
- data/lib/image/meta.rb +219 -0
- data/lib/image/routes/delete_all_images.rb +40 -0
- data/lib/image/routes/delete_image.rb +62 -0
- data/lib/image/routes/get_image.rb +78 -0
- data/lib/image/routes/get_images.rb +54 -0
- data/lib/image/routes/get_images_detail.rb +54 -0
- data/lib/image/routes/head_image.rb +51 -0
- data/lib/image/routes/post_image.rb +189 -0
- data/lib/image/routes/put_image.rb +205 -0
- data/lib/image/server.rb +307 -0
- data/lib/image/store/cumulus.rb +126 -0
- data/lib/image/store/file_system.rb +119 -0
- data/lib/image/store/hdfs.rb +149 -0
- data/lib/image/store/http.rb +78 -0
- data/lib/image/store/lunacloud.rb +126 -0
- data/lib/image/store/s3.rb +121 -0
- data/lib/image/store/store.rb +39 -0
- data/lib/image/store/walrus.rb +130 -0
- data/lib/image/version.rb +5 -0
- data/lib/visor-image.rb +30 -0
- data/spec/lib/client_spec.rb +0 -0
- data/spec/lib/meta_spec.rb +230 -0
- data/spec/lib/routes/delete_image_spec.rb +98 -0
- data/spec/lib/routes/get_image_spec.rb +78 -0
- data/spec/lib/routes/get_images_detail_spec.rb +104 -0
- data/spec/lib/routes/get_images_spec.rb +104 -0
- data/spec/lib/routes/head_image_spec.rb +51 -0
- data/spec/lib/routes/post_image_spec.rb +112 -0
- data/spec/lib/routes/put_image_spec.rb +109 -0
- data/spec/lib/server_spec.rb +62 -0
- data/spec/lib/store/cumulus_spec.rb +0 -0
- data/spec/lib/store/file_system_spec.rb +32 -0
- data/spec/lib/store/http_spec.rb +56 -0
- data/spec/lib/store/s3_spec.rb +37 -0
- data/spec/lib/store/store_spec.rb +36 -0
- data/spec/lib/store/walrus_spec.rb +0 -0
- 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
|