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
@@ -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