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
@@ -0,0 +1,149 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'em-synchrony'
4
+ require 'em-synchrony/em-http'
5
+
6
+ module Visor
7
+ module Image
8
+ module Store
9
+
10
+ # The Apache Hadoop HDFS (HDFS) backend store.
11
+ #
12
+ # This class handles the management of image files located in the HDFS storage system,
13
+ # based on a URI like *hdfs://username@s<host>:<port>/<path>/<bucket>/<image>*.
14
+ #
15
+ #http://10.0.3.12:50075/webhdfs/v1/foo/1.iso?op=OPEN&user.name=hadoop&offset=0
16
+ class HDFS
17
+ include Visor::Common::Exception
18
+
19
+ CONTEXT_ROOT="webhdfs/v1"
20
+
21
+ attr_accessor :uri, :config, :username, :bucket, :file, :base, :host, :port
22
+
23
+ # Initializes a new Cumulus store client object. Cumulus credentials are loaded from the URI,
24
+ # on GET and DELETE operations, or from the configuration file for POST and PUT operation.
25
+ #
26
+ # @param [String] uri The URI of the file location.
27
+ # @param config [Hash] A set of configurations for the wanted store, loaded from
28
+ # VISoR configuration file.
29
+ #
30
+ # @return [Object] An instantiated Cumulus store object ready to use.
31
+ #
32
+ def initialize(uri, config)
33
+ @uri = URI(uri)
34
+ @config = config[:hdfs]
35
+
36
+ if @uri.scheme
37
+ @username = @uri.user
38
+ @base = @uri.path.split('/')[1..2].join('/')
39
+ @bucket = @uri.path.split('/')[3]
40
+ @file = @uri.path.split('/')[4]
41
+ @host = @uri.host
42
+ @port = @uri.port
43
+ else
44
+ @username = @config[:username]
45
+ @bucket = @config[:bucket]
46
+ @base = CONTEXT_ROOT
47
+ @host = @config[:host]
48
+ @port = @config[:port]
49
+ end
50
+ end
51
+
52
+
53
+ # Returns the image file to clients, streamed in chunks.
54
+ #
55
+ # @return [Object] Yields the file, a chunk at time.
56
+ #
57
+ def get
58
+ uri = generate_uri('op=OPEN')
59
+ # This raises cant yield from root fiber
60
+ #res = EventMachine::HttpRequest.new(uri).get
61
+ #url = URI(res.response_header['LOCATION'])
62
+ #url.hostname = host
63
+ #http = EventMachine::HttpRequest.new(url).aget
64
+ # ...
65
+
66
+ # This works, should substitute (uri).get with (url).get in down
67
+ #require "net/http"
68
+ #req = Net::HTTP::Get.new(uri.request_uri)
69
+ #res = Net::HTTP.new(uri.hostname, uri.port).request(req)
70
+ #url = URI(res['location'])
71
+ #url.hostname = host
72
+ #STDERR.puts "URL #{url}"
73
+ # ...
74
+
75
+ # This solves it by manually defining the final location (try to solve the error above)
76
+ uri.port = 50075
77
+ uri = uri.to_s + '&offset=0'
78
+
79
+ http = EventMachine::HttpRequest.new(uri).aget
80
+ finish = proc { yield nil }
81
+ http.stream { |chunk| yield chunk }
82
+ http.callback &finish
83
+ http.errback &finish
84
+ end
85
+
86
+ # Saves the image file to the its final destination, based on the temporary file
87
+ # created by the server at data reception time.
88
+ #
89
+ # @param [String] id The image id.
90
+ # @param [File] tmp_file The temporary file descriptor.
91
+ # @param [String] format The image file format.
92
+ #
93
+ # @return [String, Integer] The generated file location URI and image file size.
94
+ #
95
+ # @raise [Duplicated] If the image file already exists.
96
+ #
97
+ def save(id, tmp_file, format)
98
+ @file = "#{id}.#{format}"
99
+ uri = "hdfs://#{username}@#{host}:#{port}/#{base}/#{bucket}/#{file}"
100
+ size = tmp_file.size
101
+
102
+ path = generate_uri('op=CREATE&overwrite=true')
103
+ http = EventMachine::HttpRequest.new(path).put
104
+ location = URI(http.response_header['LOCATION'])
105
+
106
+ location.hostname = host
107
+ raise Duplicated, "The image file #{uri} already exists" if file_exists?(false)
108
+ STDERR.puts "COPYING!!"
109
+
110
+ EventMachine::HttpRequest.new(location).put :file => tmp_file.path
111
+ [uri, size]
112
+ end
113
+
114
+ # Deletes the image file from its location.
115
+ #
116
+ # @raise [NotFound] If the image file was not found.
117
+ #
118
+ def delete
119
+ uri = generate_uri('op=DELETE&recursive=true')
120
+ EventMachine::HttpRequest.new(uri).delete
121
+ end
122
+
123
+ # Check if the image file exists.
124
+ #
125
+ # @param [True, False] raise_exc (true) If it should raise exception or return
126
+ # true/false whether the file exists or not.
127
+ #
128
+ # @return [True, False] If raise_exc is false, return true/false whether the
129
+ # file exists or not.
130
+ #
131
+ # @raise [NotFound] If the image file was not found.
132
+ #
133
+ def file_exists?(raise_exc=true)
134
+ uri = generate_uri('op=GETFILESTATUS')
135
+ req = Net::HTTP::Get.new(uri.request_uri)
136
+ res = Net::HTTP.new(uri.hostname, uri.port).request(req)
137
+ exist = res.is_a? Net::HTTPSuccess
138
+ raise NotFound, "No image file found at #{uri}" if raise_exc && !exist
139
+ exist
140
+ end
141
+
142
+ def generate_uri(params)
143
+ URI("http://#{host}:#{port}/#{base}/#{bucket}/#{file}?#{params}&user.name=#{username}")
144
+ end
145
+ end
146
+
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,78 @@
1
+ require "em-synchrony"
2
+ require "em-synchrony/em-http"
3
+
4
+ module Visor
5
+ module Image
6
+ module Store
7
+
8
+ # The HTTP backend store.
9
+ #
10
+ # This class handles the management of image files located in a remote HTTP location,
11
+ # based on a URI like *'http://www.domain.com/path-to-image-file'*.
12
+ #
13
+ # Useful for point an image to the last release of some distro, like:
14
+ # 'http://www.ubuntu.com/start-download?distro=server&bits=64&release=latest'
15
+ #
16
+ class HTTP
17
+ include Visor::Common::Exception
18
+
19
+ attr_accessor :uri, :config
20
+
21
+ # Initializes a new HTTP store client object.
22
+ #
23
+ # @param [String] uri The URI of the file location.
24
+ # @param [Hash] config (nil) A set of configurations for the wanted store,
25
+ # loaded from VISoR configuration file.
26
+ #
27
+ # @return [Object] An instantiated HTTP store object ready to use.
28
+ #
29
+ def initialize(uri, config=nil)
30
+ @uri = uri
31
+ @config = config
32
+ end
33
+
34
+ # Returns the image file to clients, streamed in chunks.
35
+ #
36
+ # @return [Object] Yields the file, a chunk at time.
37
+ #
38
+ def get
39
+ http = EventMachine::HttpRequest.new(uri).get
40
+ finish = proc { yield nil }
41
+
42
+ http.stream { |chunk| yield chunk }
43
+ http.callback &finish
44
+ http.errback &finish
45
+ end
46
+
47
+ # Check if the image file exists. This will follow redirection to a
48
+ # nested deepness of 5 levels. It will also try to follow the location header
49
+ # if any.
50
+ #
51
+ # Also, after finding the real location of the HTTP file, it will parse the file
52
+ # metadata, most properly the size and checksum, based on URL headers.
53
+ #
54
+ # @return [String] The discovered file checksum and size.
55
+ #
56
+ # @raise [NotFound] If the image file was not found.
57
+ #
58
+ def file_exists?(raise_exc=true)
59
+ http = EventMachine::HttpRequest.new(uri, connect_timeout: 2, redirects: 5).head
60
+
61
+ if location = http.response_header['LOCATION']
62
+ http = EventMachine::HttpRequest.new(location, connect_timeout: 2).head
63
+ end
64
+
65
+ exist = (http.response_header.status == 200)
66
+ length = http.response_header['CONTENT_LENGTH']
67
+ size = length.nil? ? nil : length.to_i
68
+ etag = http.response_header['ETAG']
69
+ checksum = etag.nil? ? '' : etag.gsub('"', '')
70
+
71
+ raise NotFound, "No image file found at #{uri}" if raise_exc && !exist
72
+ [exist, size, checksum]
73
+ end
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,126 @@
1
+ require 'uri'
2
+ require 's3restful'
3
+ require 'em-synchrony'
4
+ require 'em-synchrony/em-http'
5
+
6
+ module Visor
7
+ module Image
8
+ module Store
9
+
10
+ # The Lunacloud backend store.
11
+ #
12
+ # This class handles the management of image files located in the Lunacloud storage system,
13
+ # based on a URI like *lunacloud://<access_key>:<secret_key>@<host>:<port>/<bucket>/<image>*.
14
+ #
15
+ class Lunacloud
16
+ include Visor::Common::Exception
17
+
18
+ attr_accessor :uri, :config, :access_key, :secret_key, :bucket, :file, :host, :port
19
+
20
+ # Initializes a new Lunacloud store client object. Lunacloud credentials are loaded from the URI,
21
+ # on GET and DELETE operations, or from the configuration file for POST and PUT operation.
22
+ #
23
+ # @param [String] uri The URI of the file location.
24
+ # @param config [Hash] A set of configurations for the wanted store, loaded from
25
+ # VISoR configuration file.
26
+ #
27
+ # @return [Object] An instantiated Lunacloud store object ready to use.
28
+ #
29
+ def initialize(uri, config)
30
+ @uri = URI(uri)
31
+ @config = config[:lunacloud]
32
+
33
+ if @uri.scheme
34
+ @access_key = @uri.user
35
+ @secret_key = @uri.password
36
+ @bucket = @uri.path.split('/')[1]
37
+ @file = @uri.path.split('/')[2]
38
+ @host = @uri.host
39
+ @port = @uri.port
40
+ else
41
+ @access_key = @config[:access_key]
42
+ @secret_key = @config[:secret_key]
43
+ @bucket = @config[:bucket]
44
+ @host = @config[:host]
45
+ @port = @config[:port]
46
+ end
47
+ end
48
+
49
+ # Returns a Happening library S3 Lunacloud compatible connection object.
50
+ #
51
+ # @return [Happening::S3::Item] A new Lunacloud connection object.
52
+ #
53
+ def connection
54
+ S3restful::S3::Item.new(bucket, file, server: host, port: port, protocol: 'http',
55
+ aws_access_key_id: access_key, aws_secret_access_key: secret_key)
56
+ end
57
+
58
+ # Returns the image file to clients, streamed in chunks.
59
+ #
60
+ # @return [Object] Yields the file, a chunk at time.
61
+ #
62
+ def get
63
+ s3 = connection.aget
64
+ finish = proc { yield nil }
65
+
66
+ s3.stream { |chunk| yield chunk }
67
+ s3.callback &finish
68
+ s3.errback &finish
69
+ end
70
+
71
+ # Saves the image file to the its final destination, based on the temporary file
72
+ # created by the server at data reception time.
73
+ #
74
+ # @param [String] id The image id.
75
+ # @param [File] tmp_file The temporary file descriptor.
76
+ # @param [String] format The image file format.
77
+ #
78
+ # @return [String, Integer] The generated file location URI and image file size.
79
+ #
80
+ # @raise [Duplicated] If the image file already exists.
81
+ #
82
+ def save(id, tmp_file, format)
83
+ @file = "#{id}.#{format}"
84
+ uri = "lunacloud://#{access_key}:#{secret_key}@#{host}:#{port}/#{bucket}/#{file}"
85
+ size = tmp_file.size
86
+
87
+ raise Duplicated, "The image file #{fp} already exists" if file_exists?(false)
88
+ STDERR.puts "COPYING!!"
89
+
90
+ connection.store tmp_file.path
91
+
92
+ [uri, size]
93
+ end
94
+
95
+ # Deletes the image file from its location.
96
+ #
97
+ # @raise [NotFound] If the image file was not found.
98
+ #
99
+ def delete
100
+ connection.delete
101
+ end
102
+
103
+ # Check if the image file exists.
104
+ #
105
+ # @param [True, False] raise_exc (true) If it should raise exception or return
106
+ # true/false whether the file exists or not.
107
+ #
108
+ # @return [True, False] If raise_exc is false, return true/false whether the
109
+ # file exists or not.
110
+ #
111
+ # @raise [NotFound] If the image file was not found.
112
+ #
113
+ def file_exists?(raise_exc=true)
114
+ exist = nil
115
+ error = proc { exist = false }
116
+ success = proc { |res| exist = true if res.response_header.status == 200 }
117
+
118
+ connection.head(on_error: error, on_success: success)
119
+ raise NotFound, "No image file found at #{uri}" if raise_exc && !exist
120
+ exist
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,121 @@
1
+ require 'uri'
2
+ require 's3restful'
3
+ require 'em-synchrony'
4
+ require 'em-synchrony/em-http'
5
+
6
+ module Visor
7
+ module Image
8
+ module Store
9
+
10
+ # The Amazon Simple Storage (S3) backend store.
11
+ #
12
+ # This class handles the management of image files located in the S3 storage system,
13
+ # based on a URI like *s3://<access_key>:<secret_key@s3.amazonaws.com/<bucket>/<image>*.
14
+ #
15
+ class S3
16
+ include Visor::Common::Exception
17
+
18
+ attr_accessor :uri, :config, :access_key, :secret_key, :bucket, :file
19
+
20
+ # Initializes a new S3 store client object. S3 credentials are loaded from the URI,
21
+ # on GET and DELETE operations, or from the configuration file for POST and PUT operation.
22
+ #
23
+ # @param [String] uri The URI of the file location.
24
+ # @param config [Hash] A set of configurations for the wanted store, loaded from
25
+ # VISoR configuration file.
26
+ #
27
+ # @return [Object] An instantiated S3 store object ready to use.
28
+ #
29
+ def initialize(uri, config)
30
+ @uri = URI(uri)
31
+ @config = config[:s3]
32
+
33
+ if @uri.scheme
34
+ @access_key = @uri.user
35
+ @secret_key = @uri.password
36
+ @bucket = @uri.path.split('/')[1]
37
+ @file = @uri.path.split('/')[2]
38
+ else
39
+ @access_key = @config[:access_key]
40
+ @secret_key = @config[:secret_key]
41
+ @bucket = @config[:bucket]
42
+ end
43
+ end
44
+
45
+ # Returns a Happening library S3 connection object.
46
+ #
47
+ # @return [Happening::S3::Item] A new S3 connection object.
48
+ #
49
+ def connection
50
+ S3restful::S3::Item.new(bucket, file, aws_access_key_id: access_key, aws_secret_access_key: secret_key)
51
+ end
52
+
53
+ # Returns the image file to clients, streamed in chunks.
54
+ #
55
+ # @return [Object] Yields the file, a chunk at time.
56
+ #
57
+ def get
58
+ s3 = connection.aget
59
+ finish = proc { yield nil }
60
+
61
+ s3.stream { |chunk| yield chunk }
62
+ s3.callback &finish
63
+ s3.errback &finish
64
+ end
65
+
66
+ # Saves the image file to the its final destination, based on the temporary file
67
+ # created by the server at data reception time.
68
+ #
69
+ # @param [String] id The image id.
70
+ # @param [File] tmp_file The temporary file descriptor.
71
+ # @param [String] format The image file format.
72
+ #
73
+ # @return [String, Integer] The generated file location URI and image file size.
74
+ #
75
+ # @raise [Duplicated] If the image file already exists.
76
+ #
77
+ def save(id, tmp_file, format)
78
+ @file = "#{id}.#{format}"
79
+ uri = "s3://#{access_key}:#{secret_key}@s3.amazonaws.com/#{bucket}/#{file}"
80
+ size = tmp_file.size
81
+
82
+ raise Duplicated, "The image file #{fp} already exists" if file_exists?(false)
83
+ STDERR.puts "COPYING!!"
84
+
85
+ connection.store tmp_file.path
86
+
87
+ [uri, size]
88
+ end
89
+
90
+ # Deletes the image file from its location.
91
+ #
92
+ # @raise [NotFound] If the image file was not found.
93
+ #
94
+ def delete
95
+ connection.delete
96
+ end
97
+
98
+ # Check if the image file exists.
99
+ #
100
+ # @param [True, False] raise_exc (true) If it should raise exception or return
101
+ # true/false whether the file exists or not.
102
+ #
103
+ # @return [True, False] If raise_exc is false, return true/false whether the
104
+ # file exists or not.
105
+ #
106
+ # @raise [NotFound] If the image file was not found.
107
+ #
108
+ def file_exists?(raise_exc=true)
109
+ exist = nil
110
+ error = proc { exist = false }
111
+ success = proc { |res| exist = true if res.response_header.status == 200 }
112
+
113
+ connection.head(on_error: error, on_success: success)
114
+ raise NotFound, "No image file found at #{uri}" if raise_exc && !exist
115
+ exist
116
+ end
117
+ end
118
+
119
+ end
120
+ end
121
+ end