openstack 1.0.0
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/COPYING +7 -0
- data/README.rdoc +189 -0
- data/VERSION +1 -0
- data/lib/openstack.rb +107 -0
- data/lib/openstack/compute/address.rb +39 -0
- data/lib/openstack/compute/connection.rb +207 -0
- data/lib/openstack/compute/flavor.rb +35 -0
- data/lib/openstack/compute/image.rb +73 -0
- data/lib/openstack/compute/metadata.rb +116 -0
- data/lib/openstack/compute/personalities.rb +23 -0
- data/lib/openstack/compute/server.rb +249 -0
- data/lib/openstack/connection.rb +462 -0
- data/lib/openstack/swift/connection.rb +185 -0
- data/lib/openstack/swift/container.rb +214 -0
- data/lib/openstack/swift/storage_object.rb +311 -0
- metadata +96 -0
@@ -0,0 +1,185 @@
|
|
1
|
+
#Initial version of this code is based on and refactored from the rackspace/ruby-cloudfiles repo
|
2
|
+
#@ https://github.com/rackspace/ruby-cloudfiles - Copyright (c) 2011, Rackspace US, Inc.
|
3
|
+
# See COPYING for license information
|
4
|
+
#
|
5
|
+
module OpenStack
|
6
|
+
module Swift
|
7
|
+
class Connection
|
8
|
+
|
9
|
+
attr_accessor :connection
|
10
|
+
|
11
|
+
def initialize(connection)
|
12
|
+
@connection = connection
|
13
|
+
OpenStack::Authentication.init(@connection)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns true if the authentication was successful and returns false otherwise.
|
17
|
+
#
|
18
|
+
# cf.authok?
|
19
|
+
# => true
|
20
|
+
def authok?
|
21
|
+
@connection.authok
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns an OpenStack::Swift::Container object that can be manipulated easily.
|
25
|
+
# Throws a OpenStack::Exception::ItemNotFound if the container doesn't exist.
|
26
|
+
#
|
27
|
+
# container = cf.container('test')
|
28
|
+
# container.count
|
29
|
+
# => 2
|
30
|
+
# container = cf.container("no_such_container")
|
31
|
+
# => OpenStack::Exception::ItemNotFound: The resource could not be found
|
32
|
+
#
|
33
|
+
def container(name)
|
34
|
+
OpenStack::Swift::Container.new(self, name)
|
35
|
+
end
|
36
|
+
alias :get_container :container
|
37
|
+
|
38
|
+
# Sets instance variables for the bytes of storage used for this account/connection, as well as the number of containers
|
39
|
+
# stored under the account. Returns a hash with :bytes and :count keys, and also sets the instance variables.
|
40
|
+
#
|
41
|
+
# cf.get_info
|
42
|
+
# => {:count=>8, :bytes=>42438527}
|
43
|
+
# cf.bytes
|
44
|
+
# => 42438527
|
45
|
+
# Hostname of the storage server
|
46
|
+
def get_info
|
47
|
+
raise OpenStack::Exception::Authentication, "Not authenticated" unless authok?
|
48
|
+
response = @connection.req("HEAD", "")
|
49
|
+
@bytes = response["x-account-bytes-used"].to_i
|
50
|
+
@count = response["x-account-container-count"].to_i
|
51
|
+
{:bytes => @bytes, :count => @count}
|
52
|
+
end
|
53
|
+
|
54
|
+
# The total size in bytes under this connection
|
55
|
+
def bytes
|
56
|
+
get_info[:bytes]
|
57
|
+
end
|
58
|
+
|
59
|
+
# The total number of containers under this connection
|
60
|
+
def count
|
61
|
+
get_info[:count]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Gathers a list of the containers that exist for the account and returns the list of container names
|
65
|
+
# as an array. If no containers exist, an empty array is returned.
|
66
|
+
#
|
67
|
+
# If you supply the optional limit and marker parameters, the call will return the number of containers
|
68
|
+
# specified in limit, starting after the object named in marker.
|
69
|
+
#
|
70
|
+
# cf.containers
|
71
|
+
# => ["backup", "Books", "cftest", "test", "video", "webpics"]
|
72
|
+
#
|
73
|
+
# cf.containers(2,'cftest')
|
74
|
+
# => ["test", "video"]
|
75
|
+
def containers(limit = nil, marker = nil)
|
76
|
+
path = OpenStack.get_query_params({:limit=>limit, :marker=>marker}, [:limit, :marker], "")
|
77
|
+
response = @connection.req("GET", URI.encode(path))
|
78
|
+
OpenStack.symbolize_keys(JSON.parse(response.body)).inject([]){|res,cur| res << cur[:name]; res }
|
79
|
+
end
|
80
|
+
alias :list_containers :containers
|
81
|
+
|
82
|
+
# Retrieves a list of containers on the account along with their sizes (in bytes) and counts of the objects
|
83
|
+
# held within them. If no containers exist, an empty hash is returned.
|
84
|
+
#
|
85
|
+
# If you supply the optional limit and marker parameters, the call will return the number of containers
|
86
|
+
# specified in limit, starting after the object named in marker.
|
87
|
+
#
|
88
|
+
# cf.containers_detail
|
89
|
+
# => { "container1" => { :bytes => "36543", :count => "146" },
|
90
|
+
# "container2" => { :bytes => "105943", :count => "25" } }
|
91
|
+
def containers_detail(limit = nil, marker = nil)
|
92
|
+
path = OpenStack.get_query_params({:limit=>limit, :marker=>marker}, [:limit, :marker], "")
|
93
|
+
response = @connection.req("GET", URI.encode(path))
|
94
|
+
OpenStack.symbolize_keys(JSON.parse(response.body)).inject({}){|res,current| res.merge!({current[:name]=>{:bytes=>current[:bytes].to_s,:count=>current[:count].to_s}}) ; res }
|
95
|
+
end
|
96
|
+
alias :list_containers_info :containers_detail
|
97
|
+
|
98
|
+
# Returns true if the requested container exists and returns false otherwise.
|
99
|
+
#
|
100
|
+
# cf.container_exists?('good_container')
|
101
|
+
# => true
|
102
|
+
#
|
103
|
+
# cf.container_exists?('bad_container')
|
104
|
+
# => false
|
105
|
+
def container_exists?(containername)
|
106
|
+
path = "/#{URI.encode(containername.to_s)}"
|
107
|
+
begin
|
108
|
+
response = @connection.req("HEAD", path)
|
109
|
+
rescue OpenStack::Exception::ItemNotFound
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
# Creates a new container and returns the OpenStack::Swift::Container object.
|
116
|
+
#
|
117
|
+
# "/" is not valid in a container name. The container name is limited to
|
118
|
+
# 256 characters or less.
|
119
|
+
#
|
120
|
+
# container = cf.create_container('new_container')
|
121
|
+
# container.name
|
122
|
+
# => "new_container"
|
123
|
+
#
|
124
|
+
# container = cf.create_container('bad/name')
|
125
|
+
# => OpenStack::Exception::InvalidArgument: Container name cannot contain '/'
|
126
|
+
def create_container(containername)
|
127
|
+
raise OpenStack::Exception::InvalidArgument.new("Container name cannot contain '/'") if containername.match("/")
|
128
|
+
raise OpenStack::Exception::InvalidArgument.new("Container name is limited to 256 characters") if containername.length > 256
|
129
|
+
path = "/#{URI.encode(containername.to_s)}"
|
130
|
+
@connection.req("PUT", path)
|
131
|
+
OpenStack::Swift::Container.new(self, containername)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Deletes a container from the account. Throws a OpenStack::Exception::ResourceStateConflict
|
135
|
+
# if the container still contains objects. Throws a OpenStack::Exception::ItemNotFound if the
|
136
|
+
# container doesn't exist.
|
137
|
+
#
|
138
|
+
# cf.delete_container('new_container')
|
139
|
+
# => true
|
140
|
+
#
|
141
|
+
# cf.delete_container('video')
|
142
|
+
# => OpenStack::Exception::ResourceStateConflict: The container: "video" is not empty. There was a conflict with the state of the resource
|
143
|
+
#
|
144
|
+
#
|
145
|
+
# cf.delete_container('nonexistent')
|
146
|
+
# => OpenStack::Exception::ItemNotFound: The container: "nonexistant" does not exist. The resource could not be found
|
147
|
+
def delete_container(containername)
|
148
|
+
path = "/#{URI.encode(containername.to_s)}"
|
149
|
+
begin
|
150
|
+
@connection.req("DELETE", path)
|
151
|
+
rescue OpenStack::Exception::ResourceStateConflict => conflict
|
152
|
+
msg = "The container: \"#{containername}\" is not empty. #{conflict.message}"
|
153
|
+
raise OpenStack::Exception::ResourceStateConflict.new(msg, conflict.response_code, conflict.response_body)
|
154
|
+
rescue OpenStack::Exception::ItemNotFound => not_found
|
155
|
+
msg = "The container: \"#{containername}\" does not exist. #{not_found.message}"
|
156
|
+
raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
|
157
|
+
end
|
158
|
+
true
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
#used for PUT object with body_stream for http data
|
164
|
+
#see OpenStack::Connection::put_object
|
165
|
+
class ChunkedConnectionWrapper
|
166
|
+
def initialize(data, chunk_size)
|
167
|
+
@size = chunk_size
|
168
|
+
@file = data
|
169
|
+
end
|
170
|
+
|
171
|
+
def read(foo)
|
172
|
+
@file.read(@size)
|
173
|
+
end
|
174
|
+
|
175
|
+
def eof!
|
176
|
+
@file.eof!
|
177
|
+
end
|
178
|
+
def eof?
|
179
|
+
@file.eof?
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
#Initial version of this code is based on and refactored from the rackspace/ruby-cloudfiles repo
|
2
|
+
#@ https://github.com/rackspace/ruby-cloudfiles - Copyright (c) 2011, Rackspace US, Inc.
|
3
|
+
# See COPYING for license information
|
4
|
+
#
|
5
|
+
module OpenStack
|
6
|
+
module Swift
|
7
|
+
class Container
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
attr_reader :swift
|
11
|
+
attr_reader :metadata
|
12
|
+
|
13
|
+
def initialize(swift, name)
|
14
|
+
@swift = swift
|
15
|
+
@name = name
|
16
|
+
@metadata = container_metadata
|
17
|
+
end
|
18
|
+
|
19
|
+
# Retrieves Metadata for the container
|
20
|
+
def container_metadata
|
21
|
+
path = "/#{URI.encode(@name.to_s)}"
|
22
|
+
response = @swift.connection.req("HEAD", path)
|
23
|
+
resphash = response.to_hash
|
24
|
+
meta = {:bytes=>resphash["x-container-bytes-used"][0], :count=>resphash["x-container-object-count"][0], :metadata=>{}}
|
25
|
+
resphash.inject({}){|res, (k,v)| meta[:metadata].merge!({ k.gsub("x-container-meta-", "") => v.first }) if k.match(/^x-container-meta-/)}
|
26
|
+
meta
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the container's metadata as a Hash, stripping off the X-Meta-Object-
|
30
|
+
# prefix that OpenStack prepends to the metadata key name.
|
31
|
+
#
|
32
|
+
# object.metadata
|
33
|
+
# => {"ruby"=>"cool", "foo"=>"bar"}
|
34
|
+
def metadata
|
35
|
+
metahash = {}
|
36
|
+
self.container_metadata[:metadata].each{ |key, value| metahash[key.gsub(/x-container-meta-/, '').gsub(/%20/, ' ')] = URI.decode(value).gsub(/\+\-/, ' ') }
|
37
|
+
metahash
|
38
|
+
end
|
39
|
+
|
40
|
+
# Sets the metadata for a container. By passing a hash as an argument, you can set the metadata for an object.
|
41
|
+
# New calls to set metadata are additive. To remove metadata, set the value of the key to nil.
|
42
|
+
# Including the X-Container-Meta- prefix for each metadata key is optional:
|
43
|
+
#
|
44
|
+
# container = os.container("foo")
|
45
|
+
# container.set_metadata({"X-Container-Meta-Author"=> "msa", "version"=>"1.2", :date=>"today"})
|
46
|
+
# => true
|
47
|
+
# container.metadata
|
48
|
+
# => {"date"=>"today", "author"=>"msa", "version"=>"1.2"}
|
49
|
+
#
|
50
|
+
# Returns true if operation is successful; Throws OpenStack::Exception::ItemNotFound if the
|
51
|
+
# container doesn't exist.
|
52
|
+
#
|
53
|
+
def set_metadata(metadatahash)
|
54
|
+
headers = metadatahash.inject({}){|res, (k,v)| ((k.to_s.match /^X-Container-Meta-/i) ? res[k.to_s]=v : res["X-Container-Meta-#{k}"]=v ) ; res}
|
55
|
+
headers.merge!({'content-type'=>'application/json'})
|
56
|
+
begin
|
57
|
+
response = @swift.connection.req("POST", URI.encode("/#{@name.to_s}"), {:headers=>headers} )
|
58
|
+
true
|
59
|
+
rescue OpenStack::Exception::ItemNotFound => not_found
|
60
|
+
msg = "Cannot set metadata - container: \"#{@name}\" does not exist!. #{not_found.message}"
|
61
|
+
raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Size of the container (in bytes)
|
66
|
+
def bytes
|
67
|
+
container_metadata[:bytes]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Number of objects in the container
|
71
|
+
def count
|
72
|
+
container_metadata[:count]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the OpenStack::Swift::StorageObject for the named object.
|
76
|
+
# Refer to the OpenStack::Swift::StorageObject class for available methods.
|
77
|
+
# If the object exists, it will be returned. If the object does not exist,
|
78
|
+
# a OpenStack::Exception::ItemNotFound will be thrown.
|
79
|
+
#
|
80
|
+
# object = container.object('test.txt')
|
81
|
+
# object.data
|
82
|
+
# => "This is test data"
|
83
|
+
#
|
84
|
+
# object = container.object('newfile.txt')
|
85
|
+
# => OpenStack::Exception::ItemNotFound: No Object "newfile.txt" found in Container "another_container_foo"
|
86
|
+
#
|
87
|
+
def object(objectname)
|
88
|
+
o = OpenStack::Swift::StorageObject.new(self, objectname, true)
|
89
|
+
return o
|
90
|
+
end
|
91
|
+
alias :get_object :object
|
92
|
+
|
93
|
+
|
94
|
+
# Gathers a list of all available objects in the current container and returns an array of object names.
|
95
|
+
# container = cf.container("My Container")
|
96
|
+
# container.objects
|
97
|
+
# => [ "cat", "dog", "donkey", "monkeydir", "monkeydir/capuchin"]
|
98
|
+
# Pass a limit argument to limit the list to a number of objects:
|
99
|
+
# container.objects(:limit => 1) #=> [ "cat" ]
|
100
|
+
# Pass an marker with or without a limit to start the list at a certain object:
|
101
|
+
# container.objects(:limit => 1, :marker => 'dog') #=> [ "donkey" ]
|
102
|
+
# Pass a prefix to search for objects that start with a certain string:
|
103
|
+
# container.objects(:prefix => "do") #=> [ "dog", "donkey" ]
|
104
|
+
# Only search within a certain pseudo-filesystem path:
|
105
|
+
# container.objects(:path => 'monkeydir') #=> ["monkeydir/capuchin"]
|
106
|
+
# Only grab "virtual directories", based on a single-character delimiter (no "directory" objects required):
|
107
|
+
# container.objects(:delimiter => '/') #=> ["monkeydir"]
|
108
|
+
# All arguments to this method are optional.
|
109
|
+
#
|
110
|
+
# Returns an empty array if no object exist in the container.
|
111
|
+
# if the request fails.
|
112
|
+
def objects(params = {})
|
113
|
+
path = "/#{@name.to_s}"
|
114
|
+
path = (params.empty?)? path : OpenStack.get_query_params(params, [:limit, :marker, :prefix, :path, :delimiter], path)
|
115
|
+
response = @swift.connection.req("GET", URI.encode(path))
|
116
|
+
OpenStack.symbolize_keys(JSON.parse(response.body)).inject([]){|res, cur| res << cur[:name]; res }
|
117
|
+
end
|
118
|
+
alias :list_objects :objects
|
119
|
+
|
120
|
+
# Retrieves a list of all objects in the current container along with their size
|
121
|
+
# in bytes, hash, and content_type. If no objects exist, an empty hash is returned.
|
122
|
+
#
|
123
|
+
# Accepts the same options as 'objects' method to limit the returned set.
|
124
|
+
#
|
125
|
+
# container.objects_detail
|
126
|
+
# => {"test.txt"=>{:content_type=>"application/octet-stream",
|
127
|
+
# :hash=>"e2a6fcb4771aa3509f6b27b6a97da55b",
|
128
|
+
# :last_modified=>Mon Jan 19 10:43:36 -0600 2009,
|
129
|
+
# :bytes=>"16"},
|
130
|
+
# "new.txt"=>{:content_type=>"application/octet-stream",
|
131
|
+
# :hash=>"0aa820d91aed05d2ef291d324e47bc96",
|
132
|
+
# :last_modified=>Wed Jan 28 10:16:26 -0600 2009,
|
133
|
+
# :bytes=>"22"}
|
134
|
+
# }
|
135
|
+
def objects_detail(params = {})
|
136
|
+
path = "/#{@name.to_s}"
|
137
|
+
path = (params.empty?)? path : OpenStack.get_query_params(params, [:limit, :marker, :prefix, :path, :delimiter], path)
|
138
|
+
response = @swift.connection.req("GET", URI.encode(path))
|
139
|
+
OpenStack.symbolize_keys(JSON.parse(response.body)).inject({}){|res, current| res.merge!({current[:name]=>{:bytes=>current[:bytes].to_s, :content_type=>current[:content_type].to_s, :last_modified=>current[:last_modified], :hash=>current[:hash]}}) ; res }
|
140
|
+
end
|
141
|
+
alias :list_objects_info :objects_detail
|
142
|
+
|
143
|
+
# Returns true if a container is empty and returns false otherwise.
|
144
|
+
#
|
145
|
+
# new_container.empty?
|
146
|
+
# => true
|
147
|
+
#
|
148
|
+
# full_container.empty?
|
149
|
+
# => false
|
150
|
+
def empty?
|
151
|
+
return (container_metadata[:count].to_i == 0)? true : false
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns true if object exists and returns false otherwise.
|
155
|
+
#
|
156
|
+
# container.object_exists?('goodfile.txt')
|
157
|
+
# => true
|
158
|
+
#
|
159
|
+
# container.object_exists?('badfile.txt')
|
160
|
+
# => false
|
161
|
+
def object_exists?(objectname)
|
162
|
+
path = "/#{@name.to_s}/#{objectname}"
|
163
|
+
begin
|
164
|
+
response = @swift.connection.req("HEAD", URI.encode(path))
|
165
|
+
true
|
166
|
+
rescue OpenStack::Exception::ItemNotFound
|
167
|
+
false
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Creates a new OpenStack::Swift::StorageObject in the current container.
|
172
|
+
#
|
173
|
+
# If an object with the specified name exists in the current container, that object will be overwritten
|
174
|
+
#
|
175
|
+
#optional headers: {
|
176
|
+
# :metadata=>{key=>value, key1=>value1, ...}
|
177
|
+
# :content_type=>content type of created object
|
178
|
+
# :etag=>MD5 checksum of object data to be compared to that on server side
|
179
|
+
# :manifest=>set manifest header for segmented large object
|
180
|
+
# }
|
181
|
+
# The optional data can be a File or a String - see StorageObject.create and .write methods
|
182
|
+
def create_object(objectname, headers={}, data=nil)
|
183
|
+
OpenStack::Swift::StorageObject.create(self, objectname, headers, data)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Removes an OpenStack::Swift::StorageObject from a container.
|
187
|
+
# True is returned if the removal is successful.
|
188
|
+
# Throws OpenStack::Exception::ItemNotFound if the object doesn't exist.
|
189
|
+
#
|
190
|
+
# container.delete_object('new.txt')
|
191
|
+
# => true
|
192
|
+
#
|
193
|
+
# container.delete_object('Foo')
|
194
|
+
# =>OpenStack::Exception::ItemNotFound: The object: "Foo" does not exist in container "another_containerfoo". The resource could not be found
|
195
|
+
#
|
196
|
+
def delete_object(objectname)
|
197
|
+
path = "/#{@name.to_s}/#{objectname}"
|
198
|
+
begin
|
199
|
+
response = @swift.connection.req("DELETE", URI.encode(path))
|
200
|
+
true
|
201
|
+
rescue OpenStack::Exception::ItemNotFound => not_found
|
202
|
+
msg = "The object: \"#{objectname}\" does not exist in container \"#{@name}\". #{not_found.message}"
|
203
|
+
raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
def to_s # :nodoc:
|
209
|
+
@name
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,311 @@
|
|
1
|
+
#Initial version of this code is based on and refactored from the rackspace/ruby-cloudfiles repo
|
2
|
+
#@ https://github.com/rackspace/ruby-cloudfiles - Copyright (c) 2011, Rackspace US, Inc.
|
3
|
+
# See COPYING for license information
|
4
|
+
#
|
5
|
+
module OpenStack
|
6
|
+
module Swift
|
7
|
+
class StorageObject
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
attr_reader :container
|
11
|
+
attr_reader :metadata
|
12
|
+
|
13
|
+
# Builds a new OpenStack::Swift::StorageObject in the specified container.
|
14
|
+
# If force_exist is set, the object must exist or a
|
15
|
+
# OpenStack::Exception::ItemNotFound will be raised.
|
16
|
+
# If not, an "empty" StorageObject will be returned, ready for data
|
17
|
+
# via the write method
|
18
|
+
#
|
19
|
+
# The container parameter must be an OpenStack::Swift::Container object.
|
20
|
+
#
|
21
|
+
# This constructor is typically not called directly. You can get a reference to
|
22
|
+
# an existing Object via OpenStack::Swift::Container::object method or create a
|
23
|
+
# new Object via OpenStack::Swift::Container::create_object method
|
24
|
+
#
|
25
|
+
def initialize(container, objectname, force_exists = false)
|
26
|
+
@container = container
|
27
|
+
@containername = container.name
|
28
|
+
@name = objectname
|
29
|
+
|
30
|
+
if force_exists
|
31
|
+
raise OpenStack::Exception::ItemNotFound.new("No Object \"#{@name}\" found in Container \"#{@containername}\"", "404", "") unless container.object_exists?(objectname)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#create a new Object in a given Container
|
36
|
+
#optional headers: {
|
37
|
+
# :metadata=>{key=>value, key1=>value1, ...}
|
38
|
+
# :content_type=>content type of created object
|
39
|
+
# :etag=>MD5 checksum of object data to be compared to that on server side
|
40
|
+
# :manifest=>set manifest header for segmented large object
|
41
|
+
# }
|
42
|
+
#
|
43
|
+
# The container parameter must be an OpenStack::Swift::Container object.
|
44
|
+
# Typically you'd create an Object by first getting a Container:
|
45
|
+
# cont = os.container("foo_container")
|
46
|
+
# cont.create_object("my_new_object", {}, "object data")
|
47
|
+
#
|
48
|
+
def self.create(container, objectname, headers={}, data=nil)
|
49
|
+
provided_headers = (headers[:metadata] || {}).inject({}){|res, (k,v)| ((k.match /^X-Object-Meta-/i) ? res[k]=v : res["X-Object-Meta-#{k}"]=v) ;res}
|
50
|
+
provided_headers["content-type"] = headers[:content_type] unless headers[:content_type].nil?
|
51
|
+
provided_headers["ETag"] = headers[:etag] unless headers[:etag].nil?
|
52
|
+
provided_headers["X-Object-Manifest"] = headers[:manifest] unless headers[:manifest].nil?
|
53
|
+
if data.nil? #just create an empty object
|
54
|
+
path = "/#{container.name}/#{objectname}"
|
55
|
+
provided_headers["content-length"] = "0"
|
56
|
+
container.swift.connection.req("PUT", URI.encode(path), {:headers=>provided_headers})
|
57
|
+
else
|
58
|
+
self.new(container, objectname).write(data, provided_headers)
|
59
|
+
end
|
60
|
+
self.new(container, objectname)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Retrieves Metadata for the object
|
64
|
+
# object = container.object("conversion_helper.rb")
|
65
|
+
# => #<OpenStack::Swift::StorageObject:0xb7692488 ....
|
66
|
+
# object.object_metadata
|
67
|
+
# => {:manifest=>nil, :bytes=>"1918", :content_type=>"application/octet-stream", :metadata=>{"foo"=>"bar, "herpa"=>"derp"}, :etag=>"1e5b089a1d92052bcf759d86465143f8", :last_modified=>"Tue, 17 Apr 2012 08:46:35 GMT"}
|
68
|
+
#
|
69
|
+
def object_metadata
|
70
|
+
path = "/#{@containername}/#{@name}"
|
71
|
+
response = @container.swift.connection.req("HEAD", path)
|
72
|
+
resphash = response.to_hash
|
73
|
+
meta = { :bytes=>resphash["content-length"][0],
|
74
|
+
:content_type=>resphash["content-type"][0],
|
75
|
+
:last_modified=>resphash["last-modified"][0],
|
76
|
+
:etag=>resphash["etag"][0],
|
77
|
+
:manifest=> (resphash.has_key?("x-object-manifest") ? resphash["x-object-manifest"][0] : nil),
|
78
|
+
:metadata=>{}}
|
79
|
+
resphash.inject({}){|res, (k,v)| meta[:metadata].merge!({ k.gsub("x-object-meta-", "") => v.first }) if k.match(/^x-object-meta-/)}
|
80
|
+
meta
|
81
|
+
end
|
82
|
+
|
83
|
+
#returns just the user defined custom metadata
|
84
|
+
# obj.metadata
|
85
|
+
# => {"foo"=>"bar, "herpa"=>"derp"}
|
86
|
+
def metadata
|
87
|
+
self.object_metadata[:metadata]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Size of the object (in bytes)
|
91
|
+
# obj.bytes
|
92
|
+
# => "493009"
|
93
|
+
def bytes
|
94
|
+
self.object_metadata[:bytes]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Date of the object's last modification
|
98
|
+
# obj.last_modified
|
99
|
+
# => "Thu, 26 Apr 2012 09:22:51 GMT"
|
100
|
+
def last_modified
|
101
|
+
self.object_metadata[:last_modified]
|
102
|
+
end
|
103
|
+
|
104
|
+
# ETag of the object data
|
105
|
+
# obj.etag
|
106
|
+
# => "494e444f92a8082dabac80a74cdf2c3b"
|
107
|
+
def etag
|
108
|
+
self.object_metadata[:etag]
|
109
|
+
end
|
110
|
+
|
111
|
+
# Content type of the object data
|
112
|
+
# obj.content_type
|
113
|
+
# => "application/json"
|
114
|
+
def content_type
|
115
|
+
self.object_metadata[:content_type]
|
116
|
+
end
|
117
|
+
|
118
|
+
# Retrieves the data from an object and stores the data in memory. The data is returned as a string.
|
119
|
+
# Throws a OpenStack::Exception::ItemNotFound if the object doesn't exist.
|
120
|
+
#
|
121
|
+
# If the optional size and range arguments are provided, the call will return the number of bytes provided by
|
122
|
+
# size, starting from the offset provided in offset.
|
123
|
+
#
|
124
|
+
# object.data
|
125
|
+
# => "This is the text stored in the file"
|
126
|
+
def data(size = -1, offset = 0, headers = {})
|
127
|
+
headers = {'content-type'=>'application/json'}
|
128
|
+
if size.to_i > 0
|
129
|
+
range = sprintf("bytes=%d-%d", offset.to_i, (offset.to_i + size.to_i) - 1)
|
130
|
+
headers['Range'] = range
|
131
|
+
end
|
132
|
+
path = "/#{@containername}/#{@name}"
|
133
|
+
begin
|
134
|
+
response = @container.swift.connection.req("GET", URI.encode(path), {:headers=>headers})
|
135
|
+
response.body
|
136
|
+
rescue OpenStack::Exception::ItemNotFound => not_found
|
137
|
+
msg = "No Object \"#{@name}\" found in Container \"#{@containername}\". #{not_found.message}"
|
138
|
+
raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
alias :read :data
|
142
|
+
|
143
|
+
# Retrieves the data from an object and returns a stream that must be passed to a block.
|
144
|
+
# Throws a OpenStack::Exception::ItemNotFound if the object doesn't exist.
|
145
|
+
#
|
146
|
+
# If the optional size and range arguments are provided, the call will return the number of bytes provided by
|
147
|
+
# size, starting from the offset provided in offset.
|
148
|
+
#
|
149
|
+
# The method returns the HTTP response object
|
150
|
+
#
|
151
|
+
# data = ""
|
152
|
+
# object.data_stream do |chunk| data += chunk end
|
153
|
+
# => #<Net::HTTPOK 200 OK readbody=true>
|
154
|
+
# data
|
155
|
+
# => "This is the text stored in the file"
|
156
|
+
def data_stream(size = -1, offset = 0, &block)
|
157
|
+
headers = {'content-type'=>'application/json'}
|
158
|
+
if size.to_i > 0
|
159
|
+
range = sprintf("bytes=%d-%d", offset.to_i, (offset.to_i + size.to_i) - 1)
|
160
|
+
headers['Range'] = range
|
161
|
+
end
|
162
|
+
server = @container.swift.connection.service_host
|
163
|
+
path = @container.swift.connection.service_path + URI.encode("/#{@containername}/#{@name}")
|
164
|
+
port = @container.swift.connection.service_port
|
165
|
+
scheme = @container.swift.connection.service_scheme
|
166
|
+
response = @container.swift.connection.csreq("GET", server, path, port, scheme, headers, nil, 0, &block)
|
167
|
+
raise OpenStack::Exception.raise_exception(response) unless response.code.match(/^20.$/)
|
168
|
+
response
|
169
|
+
end
|
170
|
+
|
171
|
+
# Sets the metadata for an object. By passing a hash as an argument, you can set the metadata for an object.
|
172
|
+
# However, setting metadata will overwrite any existing metadata for the object. Returns true if the call
|
173
|
+
# was successful. Throws OpenStack::Exception::ItemNotFound if the object doesn't exist.
|
174
|
+
#
|
175
|
+
# The OpenStack mandated 'X-Object-Meta' prefix is optional:
|
176
|
+
#
|
177
|
+
# obj.set_metadata({:foo=>"bar", "X-Object-Meta-herpa"=>"derp", "author"=>"me"})
|
178
|
+
# => true
|
179
|
+
#
|
180
|
+
# obj.metadata
|
181
|
+
# => {"foo"=>"bar", "author"=>"me", "herpa"=>"derp"}
|
182
|
+
#
|
183
|
+
def set_metadata(metadatahash)
|
184
|
+
headers = metadatahash.inject({}){|res, (k,v)| ((k.to_s.match /^X-Object-Meta-/i) ? res[k.to_s]=v : res["X-Object-Meta-#{k.to_s}"]=v ) ;res}
|
185
|
+
headers['content-type'] = 'application/json'
|
186
|
+
path = "/#{@containername}/#{@name}"
|
187
|
+
begin
|
188
|
+
response = @container.swift.connection.req("POST", URI.encode(path), {:headers=>headers})
|
189
|
+
rescue OpenStack::Exception::ItemNotFound => not_found
|
190
|
+
msg = "Can't set metadata: No Object \"#{@name}\" found in Container \"#{@containername}\". #{not_found.message}"
|
191
|
+
raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
|
192
|
+
end
|
193
|
+
true
|
194
|
+
end
|
195
|
+
alias :metadata= :set_metadata
|
196
|
+
|
197
|
+
|
198
|
+
# Returns the object's manifest.
|
199
|
+
#
|
200
|
+
# object.manifest
|
201
|
+
# => "container/prefix"
|
202
|
+
def manifest
|
203
|
+
self.object_metadata[:manifest]
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
# Sets the manifest for an object. By passing a string as an argument, you can set the manifest for an object.
|
208
|
+
# However, setting manifest will overwrite any existing manifest for the object.
|
209
|
+
#
|
210
|
+
# Throws OpenStack::Exception::ItemNotFound if the object doesn't exist. Returns true if the call is successful.
|
211
|
+
#
|
212
|
+
def set_manifest(manifest)
|
213
|
+
headers = {'X-Object-Manifest' => manifest}
|
214
|
+
path = "/#{@containername}/#{@name}"
|
215
|
+
begin
|
216
|
+
response = @container.swift.connection.req("POST", URI.encode(path), {:headers=>headers})
|
217
|
+
rescue OpenStack::Exception::ItemNotFound => not_found
|
218
|
+
msg = "Can't set manifest: No Object \"#{@name}\" found in Container \"#{@containername}\". #{not_found.message}"
|
219
|
+
raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
|
220
|
+
end
|
221
|
+
true
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
# Takes supplied data and writes it to the object, saving it. You can supply an optional hash of headers, including
|
226
|
+
# Content-Type and ETag, that will be applied to the object.
|
227
|
+
#
|
228
|
+
# If you would rather stream the data in chunks, instead of reading it all into memory at once, you can pass an
|
229
|
+
# IO object for the data, such as: object.write(open('/path/to/file.mp3'))
|
230
|
+
#
|
231
|
+
# You can compute your own MD5 sum and send it in the "ETag" header. If you provide yours, it will be compared to
|
232
|
+
# the MD5 sum on the server side.
|
233
|
+
#
|
234
|
+
# Returns true on success, raises exceptions if stuff breaks.
|
235
|
+
#
|
236
|
+
# object = container.create_object("newfile.txt")
|
237
|
+
#
|
238
|
+
# object.write("This is new data")
|
239
|
+
# => true
|
240
|
+
#
|
241
|
+
# object.data
|
242
|
+
# => "This is new data"
|
243
|
+
#
|
244
|
+
#
|
245
|
+
def write(data, headers = {})
|
246
|
+
server = @container.swift.connection.service_host
|
247
|
+
path = @container.swift.connection.service_path + URI.encode("/#{@containername}/#{@name}")
|
248
|
+
port = @container.swift.connection.service_port
|
249
|
+
scheme = @container.swift.connection.service_scheme
|
250
|
+
body = (data.is_a?(String))? StringIO.new(data) : data
|
251
|
+
body.binmode if (body.respond_to?(:binmode))
|
252
|
+
response = @container.swift.connection.put_object(server, path, port, scheme, headers, body, 0)
|
253
|
+
raise OpenStack::Exception.raise_exception(response) unless response.code.match(/^20.$/)
|
254
|
+
true
|
255
|
+
end
|
256
|
+
|
257
|
+
# Copy this object to a new location (optionally in a new container)
|
258
|
+
#
|
259
|
+
# You must supply a name for the new object as well as a container name.
|
260
|
+
#
|
261
|
+
# new_object = object.copy("new_obj", "my_container")
|
262
|
+
#
|
263
|
+
# You may also supply a hash of headers to set Content-Type, or custom
|
264
|
+
# key=>value metadata:
|
265
|
+
#optional headers: {
|
266
|
+
# :metadata=>{key=>value, key1=>value1, ...}
|
267
|
+
# :content_type=>content type of created object
|
268
|
+
# }
|
269
|
+
#
|
270
|
+
# copied = object.copy('newfile.tmp', "my_container", {:content_type=>"text/plain", :metadata=>{:herp=>"derp", "X-Object-Meta-foo"=>"bar} } )
|
271
|
+
# => => #<OpenStack::Swift::StorageObject:0xb728974c .....
|
272
|
+
#
|
273
|
+
# Returns the new OpenStack::Swift::StorageObject for the copied item.
|
274
|
+
#
|
275
|
+
def copy(object_name, container_name, headers = {})
|
276
|
+
provided_headers = (headers[:metadata] || {}).inject({}){|res, (k,v)| ((k.to_s.match /^X-Object-Meta-/i) ? res[k.to_s]=v : res["X-Object-Meta-#{k.to_s}"]=v) ;res}
|
277
|
+
provided_headers["content-type"] = headers[:content_type] unless headers[:content_type].nil?
|
278
|
+
provided_headers["X-Copy-From"] = "/#{@containername}/#{@name}"
|
279
|
+
provided_headers["content-length"] = "0"
|
280
|
+
path = "/#{container_name}/#{object_name}"
|
281
|
+
begin
|
282
|
+
response = @container.swift.connection.req("PUT", URI.encode(path), {:headers=>provided_headers})
|
283
|
+
rescue OpenStack::Exception::ItemNotFound => not_found
|
284
|
+
msg = "Can't copy \"#{@name}\": No Object \"#{@name}\" found in Container \"#{@containername}\". #{not_found.message}"
|
285
|
+
raise OpenStack::Exception::ItemNotFound.new(msg, not_found.response_code, not_found.response_body)
|
286
|
+
end
|
287
|
+
OpenStack::Swift::StorageObject.new(@container.swift.container(container_name), object_name)
|
288
|
+
end
|
289
|
+
|
290
|
+
# Takes the same options as the copy method, only it does a copy followed by a delete on the original object.
|
291
|
+
#
|
292
|
+
# Returns the new OpenStack::Swift::StorageObject for the moved item.
|
293
|
+
# You should not attempt to use the old object after doing
|
294
|
+
# a move.
|
295
|
+
# optional headers: {
|
296
|
+
# :metadata=>{key=>value, key1=>value1, ...}
|
297
|
+
# :content_type=>content type of created object
|
298
|
+
# }
|
299
|
+
def move(object_name, container_name, headers={})
|
300
|
+
new_object = self.copy(object_name, container_name, headers)
|
301
|
+
@container.delete_object(@name)
|
302
|
+
new_object
|
303
|
+
end
|
304
|
+
|
305
|
+
def to_s # :nodoc:
|
306
|
+
@name
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|