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