dropmyemail-openstack 1.0.9
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 +371 -0
- data/VERSION +1 -0
- data/lib/openstack.rb +112 -0
- data/lib/openstack/compute/address.rb +68 -0
- data/lib/openstack/compute/connection.rb +412 -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 +254 -0
- data/lib/openstack/connection.rb +494 -0
- data/lib/openstack/image/connection.rb +16 -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
- data/lib/openstack/volume/connection.rb +123 -0
- data/lib/openstack/volume/snapshot.rb +25 -0
- data/lib/openstack/volume/volume.rb +31 -0
- data/test/authentication_test.rb +134 -0
- data/test/connection_test.rb +39 -0
- data/test/exception_test.rb +49 -0
- data/test/metadata_test.rb +210 -0
- data/test/servers_test.rb +210 -0
- data/test/test_helper.rb +22 -0
- metadata +123 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
module OpenStack
|
3
|
+
module Compute
|
4
|
+
|
5
|
+
class AddressList < Array
|
6
|
+
def [](index)
|
7
|
+
addresses = Array.new
|
8
|
+
if index.class == Symbol then
|
9
|
+
self.each do |address|
|
10
|
+
if address.label == index.to_s then
|
11
|
+
addresses << address
|
12
|
+
end
|
13
|
+
end
|
14
|
+
addresses
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Address
|
22
|
+
|
23
|
+
attr_reader :address
|
24
|
+
attr_reader :label
|
25
|
+
attr_reader :version
|
26
|
+
|
27
|
+
def initialize(label, address, version = 4)
|
28
|
+
@label = label
|
29
|
+
if address.class == Hash then
|
30
|
+
@address = address["addr"]
|
31
|
+
@version = address["version"]
|
32
|
+
else
|
33
|
+
@address = address
|
34
|
+
@version = version
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
NON_ROUTABLE_ADDRESSES = [IPAddr.new("10.0.0.0/8"), IPAddr.new("192.168.0.0/16"), IPAddr.new("172.16.0.0/12")]
|
39
|
+
|
40
|
+
def self.is_private?(address_string)
|
41
|
+
NON_ROUTABLE_ADDRESSES.each do |no_route|
|
42
|
+
return true if no_route.include?(address_string)
|
43
|
+
end
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
#IN: { "private"=> [{"addr"=>"10.7.206.171", "version"=>4}, {"addr"=>"15.185.160.208", "version"=>4}]}
|
48
|
+
#OUT: { "private"=> [{"addr"=>"10.7.206.171", "version"=>4}],
|
49
|
+
# "public"=> [{"addr"=>"15.185.160.208", "version"=>4}] }
|
50
|
+
def self.fix_labels(addresses_info)
|
51
|
+
addresses_info.inject({"public"=>[], "private"=>[]}) do |res, (label,address_struct_list)|
|
52
|
+
address_struct_list.each do |address_struct|
|
53
|
+
if(address_struct["version"==6])#v6 addresses are all routable...
|
54
|
+
res["public"] << address_struct
|
55
|
+
else
|
56
|
+
is_private?(address_struct["addr"])? res["private"] << address_struct : res["public"] << address_struct
|
57
|
+
end
|
58
|
+
end
|
59
|
+
res
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,412 @@
|
|
1
|
+
module OpenStack
|
2
|
+
module Compute
|
3
|
+
|
4
|
+
class Connection
|
5
|
+
|
6
|
+
attr_accessor :connection
|
7
|
+
attr_accessor :extensions
|
8
|
+
|
9
|
+
def initialize(connection)
|
10
|
+
@extensions = nil
|
11
|
+
@connection = connection
|
12
|
+
OpenStack::Authentication.init(@connection)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns true if the authentication was successful and returns false otherwise.
|
16
|
+
#
|
17
|
+
# cs.authok?
|
18
|
+
# => true
|
19
|
+
def authok?
|
20
|
+
@connection.authok
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the OpenStack::Compute::Server object identified by the given id.
|
24
|
+
#
|
25
|
+
# >> server = cs.get_server(110917)
|
26
|
+
# => #<OpenStack::Compute::Server:0x101407ae8 ...>
|
27
|
+
# >> server.name
|
28
|
+
# => "MyServer"
|
29
|
+
def get_server(id)
|
30
|
+
OpenStack::Compute::Server.new(self,id)
|
31
|
+
end
|
32
|
+
alias :server :get_server
|
33
|
+
|
34
|
+
# Returns an array of hashes, one for each server that exists under this account. The hash keys are :name and :id.
|
35
|
+
#
|
36
|
+
# You can also provide :limit and :offset parameters to handle pagination.
|
37
|
+
#
|
38
|
+
# >> cs.list_servers
|
39
|
+
# => [{:name=>"MyServer", :id=>110917}]
|
40
|
+
#
|
41
|
+
# >> cs.list_servers(:limit => 2, :offset => 3)
|
42
|
+
# => [{:name=>"demo-standingcloud-lts", :id=>168867},
|
43
|
+
# {:name=>"demo-aicache1", :id=>187853}]
|
44
|
+
def list_servers(options = {})
|
45
|
+
anti_cache_param="cacheid=#{Time.now.to_i}"
|
46
|
+
path = OpenStack.paginate(options).empty? ? "#{@connection.service_path}/servers?#{anti_cache_param}" : "#{@connection.service_path}/servers?#{OpenStack.paginate(options)}&#{anti_cache_param}"
|
47
|
+
response = @connection.csreq("GET",@connection.service_host,path,@connection.service_port,@connection.service_scheme)
|
48
|
+
OpenStack::Exception.raise_exception(response) unless response.code.match(/^20.$/)
|
49
|
+
OpenStack.symbolize_keys(JSON.parse(response.body)["servers"])
|
50
|
+
end
|
51
|
+
alias :servers :list_servers
|
52
|
+
|
53
|
+
# Returns an array of hashes with more details about each server that exists under this account. Additional information
|
54
|
+
# includes public and private IP addresses, status, hostID, and more. All hash keys are symbols except for the metadata
|
55
|
+
# hash, which are verbatim strings.
|
56
|
+
#
|
57
|
+
# You can also provide :limit and :offset parameters to handle pagination.
|
58
|
+
# >> cs.list_servers_detail
|
59
|
+
# => [{:name=>"MyServer", :addresses=>{:public=>["67.23.42.37"], :private=>["10.176.241.237"]}, :metadata=>{"MyData" => "Valid"}, :imageRef=>10, :progress=>100, :hostId=>"36143b12e9e48998c2aef79b50e144d2", :flavorRef=>1, :id=>110917, :status=>"ACTIVE"}]
|
60
|
+
#
|
61
|
+
# >> cs.list_servers_detail(:limit => 2, :offset => 3)
|
62
|
+
# => [{:status=>"ACTIVE", :imageRef=>10, :progress=>100, :metadata=>{}, :addresses=>{:public=>["x.x.x.x"], :private=>["x.x.x.x"]}, :name=>"demo-standingcloud-lts", :id=>168867, :flavorRef=>1, :hostId=>"xxxxxx"},
|
63
|
+
# {:status=>"ACTIVE", :imageRef=>8, :progress=>100, :metadata=>{}, :addresses=>{:public=>["x.x.x.x"], :private=>["x.x.x.x"]}, :name=>"demo-aicache1", :id=>187853, :flavorRef=>3, :hostId=>"xxxxxx"}]
|
64
|
+
def list_servers_detail(options = {})
|
65
|
+
path = OpenStack.paginate(options).empty? ? "#{@connection.service_path}/servers/detail" : "#{@connection.service_path}/servers/detail?#{OpenStack.paginate(options)}"
|
66
|
+
response = @connection.csreq("GET",@connection.service_host,path,@connection.service_port,@connection.service_scheme)
|
67
|
+
OpenStack::Exception.raise_exception(response) unless response.code.match(/^20.$/)
|
68
|
+
json_server_list = JSON.parse(response.body)["servers"]
|
69
|
+
json_server_list.each do |server|
|
70
|
+
server["addresses"] = OpenStack::Compute::Address.fix_labels(server["addresses"])
|
71
|
+
end
|
72
|
+
OpenStack.symbolize_keys(json_server_list)
|
73
|
+
end
|
74
|
+
alias :servers_detail :list_servers_detail
|
75
|
+
|
76
|
+
# Creates a new server instance on OpenStack Compute
|
77
|
+
#
|
78
|
+
# The argument is a hash of options. The keys :name, :flavorRef,
|
79
|
+
# and :imageRef are required; :metadata, :security_groups,
|
80
|
+
# :key_name and :personality are optional.
|
81
|
+
#
|
82
|
+
# :flavorRef and :imageRef are href strings identifying a particular
|
83
|
+
# server flavor and image to use when building the server. The :imageRef
|
84
|
+
# can either be a stock image, or one of your own created with the
|
85
|
+
# server.create_image method.
|
86
|
+
#
|
87
|
+
# The :metadata argument should be a hash of key/value pairs. This
|
88
|
+
# metadata will be applied to the server at the OpenStack Compute API level.
|
89
|
+
#
|
90
|
+
# The "Personality" option allows you to include up to five files, # of
|
91
|
+
# 10Kb or less in size, that will be placed on the created server.
|
92
|
+
# For :personality, pass a hash of the form {'local_path' => 'server_path'}.
|
93
|
+
# The file located at local_path will be base64-encoded and placed at the
|
94
|
+
# location identified by server_path on the new server.
|
95
|
+
#
|
96
|
+
# Returns a OpenStack::Compute::Server object. The root password is
|
97
|
+
# available in the adminPass instance method.
|
98
|
+
#
|
99
|
+
# >> server = cs.create_server(
|
100
|
+
# :name => 'NewServer',
|
101
|
+
# :imageRef => '3',
|
102
|
+
# :flavorRef => '1',
|
103
|
+
# :metadata => {'Racker' => 'Fanatical'},
|
104
|
+
# :personality => {'/home/bob/wedding.jpg' => '/root/wedding.jpg'},
|
105
|
+
# :key_name => "mykey",
|
106
|
+
# :security_groups => [ "devel", "test"])
|
107
|
+
# => #<OpenStack::Compute::Server:0x101229eb0 ...>
|
108
|
+
# >> server.name
|
109
|
+
# => "NewServer"
|
110
|
+
# >> server.status
|
111
|
+
# => "BUILD"
|
112
|
+
# >> server.adminPass
|
113
|
+
# => "NewServerSHMGpvI"
|
114
|
+
def create_server(options)
|
115
|
+
raise OpenStack::Exception::MissingArgument, "Server name, flavorRef, and imageRef, must be supplied" unless (options[:name] && options[:flavorRef] && options[:imageRef])
|
116
|
+
options[:personality] = Personalities.get_personality(options[:personality])
|
117
|
+
options[:security_groups] = (options[:security_groups] || []).inject([]){|res, c| res << {"name"=>c} ;res}
|
118
|
+
data = JSON.generate(:server => options)
|
119
|
+
response = @connection.csreq("POST",@connection.service_host,"#{@connection.service_path}/servers",@connection.service_port,@connection.service_scheme,{'content-type' => 'application/json'},data)
|
120
|
+
OpenStack::Exception.raise_exception(response) unless response.code.match(/^20.$/)
|
121
|
+
server_info = JSON.parse(response.body)['server']
|
122
|
+
server = OpenStack::Compute::Server.new(self,server_info['id'])
|
123
|
+
server.adminPass = server_info['adminPass']
|
124
|
+
return server
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns an array of hashes listing available server images that you have access too,
|
128
|
+
# including stock OpenStack Compute images and any that you have created. The "id" key
|
129
|
+
# in the hash can be used where imageRef is required. You can also provide :limit and
|
130
|
+
# :offset parameters to handle pagination.
|
131
|
+
#
|
132
|
+
# >> cs.list_images
|
133
|
+
# => [{:name=>"CentOS 5.2", :id=>2, :updated=>"2009-07-20T09:16:57-05:00", :status=>"ACTIVE", :created=>"2009-07-20T09:16:57-05:00"},
|
134
|
+
# {:name=>"Gentoo 2008.0", :id=>3, :updated=>"2009-07-20T09:16:57-05:00", :status=>"ACTIVE", :created=>"2009-07-20T09:16:57-05:00"},...
|
135
|
+
#
|
136
|
+
# >> cs.list_images(:limit => 3, :offset => 2)
|
137
|
+
# => [{:status=>"ACTIVE", :name=>"Fedora 11 (Leonidas)", :updated=>"2009-12-08T13:50:45-06:00", :id=>13},
|
138
|
+
# {:status=>"ACTIVE", :name=>"CentOS 5.3", :updated=>"2009-08-26T14:59:52-05:00", :id=>7},
|
139
|
+
# {:status=>"ACTIVE", :name=>"CentOS 5.4", :updated=>"2009-12-16T01:02:17-06:00", :id=>187811}]
|
140
|
+
def list_images(options = {})
|
141
|
+
path = OpenStack.paginate(options).empty? ? "#{@connection.service_path}/images/detail" : "#{@connection.service_path}/images/detail?#{OpenStack.paginate(options)}"
|
142
|
+
response = @connection.csreq("GET",@connection.service_host,path,@connection.service_port,@connection.service_scheme)
|
143
|
+
OpenStack::Exception.raise_exception(response) unless response.code.match(/^20.$/)
|
144
|
+
OpenStack.symbolize_keys(JSON.parse(response.body)['images'])
|
145
|
+
end
|
146
|
+
alias :images :list_images
|
147
|
+
|
148
|
+
# Returns a OpenStack::Compute::Image object for the image identified by the provided id.
|
149
|
+
#
|
150
|
+
# >> image = cs.get_image(8)
|
151
|
+
# => #<OpenStack::Compute::Image:0x101659698 ...>
|
152
|
+
def get_image(id)
|
153
|
+
OpenStack::Compute::Image.new(self,id)
|
154
|
+
end
|
155
|
+
alias :image :get_image
|
156
|
+
|
157
|
+
# Returns an array of hashes listing all available server flavors. The :id key in the hash can be used when flavorRef is required.
|
158
|
+
#
|
159
|
+
# You can also provide :limit and :offset parameters to handle pagination.
|
160
|
+
#
|
161
|
+
# >> cs.list_flavors
|
162
|
+
# => [{:name=>"256 server", :id=>1, :ram=>256, :disk=>10},
|
163
|
+
# {:name=>"512 server", :id=>2, :ram=>512, :disk=>20},...
|
164
|
+
#
|
165
|
+
# >> cs.list_flavors(:limit => 3, :offset => 2)
|
166
|
+
# => [{:ram=>1024, :disk=>40, :name=>"1GB server", :id=>3},
|
167
|
+
# {:ram=>2048, :disk=>80, :name=>"2GB server", :id=>4},
|
168
|
+
# {:ram=>4096, :disk=>160, :name=>"4GB server", :id=>5}]
|
169
|
+
def list_flavors(options = {})
|
170
|
+
path = OpenStack.paginate(options).empty? ? "#{@connection.service_path}/flavors/detail" : "#{@connection.service_path}/flavors/detail?#{OpenStack.paginate(options)}"
|
171
|
+
response = @connection.csreq("GET",@connection.service_host,path,@connection.service_port,@connection.service_scheme)
|
172
|
+
OpenStack::Exception.raise_exception(response) unless response.code.match(/^20.$/)
|
173
|
+
OpenStack.symbolize_keys(JSON.parse(response.body)['flavors'])
|
174
|
+
end
|
175
|
+
alias :flavors :list_flavors
|
176
|
+
|
177
|
+
# Returns a OpenStack::Compute::Flavor object for the flavor identified by the provided ID.
|
178
|
+
#
|
179
|
+
# >> flavor = cs.flavor(1)
|
180
|
+
# => #<OpenStack::Compute::Flavor:0x10156dcc0 @name="256 server", @disk=10, @id=1, @ram=256>
|
181
|
+
def get_flavor(id)
|
182
|
+
OpenStack::Compute::Flavor.new(self,id)
|
183
|
+
end
|
184
|
+
alias :flavor :get_flavor
|
185
|
+
|
186
|
+
# Returns the current state of the programatic API limits. Each account has certain limits on the number of resources
|
187
|
+
# allowed in the account, and a rate of API operations.
|
188
|
+
#
|
189
|
+
# The operation returns a hash. The :absolute hash key reveals the account resource limits, including the maxmimum
|
190
|
+
# amount of total RAM that can be allocated (combined among all servers), the maximum members of an IP group, and the
|
191
|
+
# maximum number of IP groups that can be created.
|
192
|
+
#
|
193
|
+
# The :rate hash key returns an array of hashes indicating the limits on the number of operations that can be performed in a
|
194
|
+
# given amount of time. An entry in this array looks like:
|
195
|
+
#
|
196
|
+
# {:regex=>"^/servers", :value=>50, :verb=>"POST", :remaining=>50, :unit=>"DAY", :resetTime=>1272399820, :URI=>"/servers*"}
|
197
|
+
#
|
198
|
+
# This indicates that you can only run 50 POST operations against URLs in the /servers URI space per day, we have not run
|
199
|
+
# any operations today (50 remaining), and gives the Unix time that the limits reset.
|
200
|
+
#
|
201
|
+
# Another example is:
|
202
|
+
#
|
203
|
+
# {:regex=>".*", :value=>10, :verb=>"PUT", :remaining=>10, :unit=>"MINUTE", :resetTime=>1272399820, :URI=>"*"}
|
204
|
+
#
|
205
|
+
# This says that you can run 10 PUT operations on all possible URLs per minute, and also gives the number remaining and the
|
206
|
+
# time that the limit resets.
|
207
|
+
#
|
208
|
+
# Use this information as you're building your applications to put in relevant pauses if you approach your API limitations.
|
209
|
+
def limits
|
210
|
+
response = @connection.csreq("GET",@connection.service_host,"#{@connection.service_path}/limits",@connection.service_port,@connection.service_scheme)
|
211
|
+
OpenStack::Exception.raise_exception(response) unless response.code.match(/^20.$/)
|
212
|
+
OpenStack.symbolize_keys(JSON.parse(response.body)['limits'])
|
213
|
+
end
|
214
|
+
|
215
|
+
# ==============================
|
216
|
+
# API EXTENSIONS
|
217
|
+
#
|
218
|
+
# http://nova.openstack.org/api_ext/index.html
|
219
|
+
# http://api.openstack.org/ (grep 'Compute API Extensions')
|
220
|
+
#
|
221
|
+
|
222
|
+
|
223
|
+
#query the openstack provider for any implemented extensions to the compute API
|
224
|
+
#returns a hash with openstack service provider's returned details
|
225
|
+
#about the implemented extensions, e.g.:
|
226
|
+
#
|
227
|
+
# { :os-floating_ips => { :links=>[],
|
228
|
+
# :updated=>"2011-06-16T00:00:00+00:00",
|
229
|
+
# :description=>"Floating IPs support",
|
230
|
+
# :namespace=>"http://docs.openstack.org/ext/floating_ips/api/v1.1",
|
231
|
+
# :name=>"Floating_ips", :alias=>"os-floating-ips"},
|
232
|
+
# :os-keypairs => { :links=>[],
|
233
|
+
# :updated=>"2011-08-08T00:00:00+00:00",
|
234
|
+
# :description=>"Keypair Support",
|
235
|
+
# :namespace=>"http://docs.openstack.org/ext/keypairs/api/v1.1",
|
236
|
+
# :name=>"Keypairs",
|
237
|
+
# :alias=>"os-keypairs"}
|
238
|
+
# }
|
239
|
+
#
|
240
|
+
def api_extensions
|
241
|
+
if @extensions.nil?
|
242
|
+
response = @connection.req("GET", "/extensions")
|
243
|
+
OpenStack::Exception.raise_exception(response) unless response.code.match(/^20.$/)
|
244
|
+
res = OpenStack.symbolize_keys(JSON.parse(response.body))
|
245
|
+
@extensions = res[:extensions].inject({}){|result, c| result[c[:alias].to_sym] = c ; result}
|
246
|
+
end
|
247
|
+
@extensions
|
248
|
+
end
|
249
|
+
|
250
|
+
# Retrieve a list of key pairs associated with the current authenticated account
|
251
|
+
# Will return a hash:
|
252
|
+
# { :key_one => { :fingerprint => "3f:12:4d:d1:54:f1:f4:3f:fe:a8:12:ec:1a:fb:35:b2",
|
253
|
+
# :public_key => "ssh-rsa AAAAB3Nza923kJU123AADAQABAAAAg928JUwydszi029kIJudfOzQ7o160Ll1ItybDzYYcCAJ/N02loIKJU17264520bmXZFSsaZf2ErX3nSBNI3K+2zQzu832jkhkfdsa7GHH5hvNOxO7u800894312JKLJLHP/R91fdsajHKKJADSAgQ== nova@nv-zz2232-api0002\n",
|
254
|
+
# :name => "key_one"},
|
255
|
+
#
|
256
|
+
# :key_two => { :fingerprint => "6b:32:dd:d2:51:c1:f2:3a:fb:a2:52:3a:1a:bb:25:1b",
|
257
|
+
# :public_key => "ssh-rsa AKIJUuw71645kJU123AADAQABAAAAg928019oiUJY12765IJudfOzQ7o160Ll1ItybDzYYcCAJ/N80438012480321jhkhKJlfdsazu832jkhkfdsa7GHH5fdasfdsajlj2999789799987989894312JKLJLHP/R91fdsajHKKJADSAgQ== nova@bv-fdsa32-api0002\n",
|
258
|
+
# :name => "key_two"}
|
259
|
+
# }
|
260
|
+
#
|
261
|
+
# Raises OpenStack::Exception::NotImplemented if the current provider doesn't
|
262
|
+
# offer the os-keypairs extension
|
263
|
+
#
|
264
|
+
def keypairs
|
265
|
+
begin
|
266
|
+
response = @connection.req("GET", "/os-keypairs")
|
267
|
+
res = OpenStack.symbolize_keys(JSON.parse(response.body))
|
268
|
+
res[:keypairs].inject({}){|result, c| result[c[:keypair][:name].to_sym] = c[:keypair] ; result }
|
269
|
+
rescue OpenStack::Exception::ItemNotFound => not_found
|
270
|
+
msg = "The os-keypairs extension is not implemented for the provider you are talking to "+
|
271
|
+
"- #{@connection.http.keys.first}"
|
272
|
+
raise OpenStack::Exception::NotImplemented.new(msg, 501, "#{not_found.message}")
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Create a new keypair for use with launching servers. Raises
|
277
|
+
# a OpenStack::Exception::NotImplemented if os-keypairs extension
|
278
|
+
# is not implemented (or not advertised) by the OpenStack provider.
|
279
|
+
#
|
280
|
+
# The 'name' parameter MUST be supplied, otherwise a
|
281
|
+
# OpenStack::Exception::MissingArgument will be raised.
|
282
|
+
#
|
283
|
+
# Optionally requests can specify a 'public_key' parameter,
|
284
|
+
# with the full public ssh key (String) to be used to create the keypair
|
285
|
+
# (i.e. import a existing key).
|
286
|
+
#
|
287
|
+
# Returns a hash with details of the new key; the :private_key attribute
|
288
|
+
# must be saved must be saved by caller (not retrievable thereafter).
|
289
|
+
# NOTE: when the optional :public_key parameter is given, the response
|
290
|
+
# will obviously NOT contain the :private_key attribute.
|
291
|
+
#
|
292
|
+
# >> os.create_keypair({:name=>"test_key"})
|
293
|
+
# => { :name => "test_key",
|
294
|
+
# :fingerprint => "f1:f3:a2:d3:ca:75:da:f1:06:f4:f7:dc:cc:7d:e1:ca",
|
295
|
+
# :user_id => "dev_41247879706381",
|
296
|
+
# :public_key => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDgGhDH3z9uMAvPV8ziE9BCEjHCPXGufy5bOgY5mY5jOSfdmKspdbl0z/LimHVKRDNX6HoL5qRg5V/tGH/NYP5sX2zF/XRKz16lfBxiUL1EONXA9fsTEBR3FGp8NcA7hW2+YiUxWafms4If3NFqttTQ11XqTU8JCMvms4D81lhbiQ== nova@use03147k5-eth0\n",
|
297
|
+
# :private_key => "-----BEGIN RSA PRIVATE KEY-----\nMIICXwIBAAKBgQDgGhDH3z9uMAvPV8ziE9BCEjHCPXGufy5bOgY5mY5jOSfdmKsp\ndbl0z/LimHVKRDNX6HoL5qRg5V/tGH/NYP5sX2zF/XRKz16lfBxiUL1EONXA9fsT\nEBR3FGp8NcA7hW2+YiUxWafms4If3NFqttTQ11XqTU8JCMvms4D81lhbiQIDAQAB\nAoGBAJ1akAfXwNEc2V4IV2sy4FtULS4nOKh+0szpjC9rm+gd3Nki9qQQ7lyQGwpy\nZID2LFsAeJnco/UJefaf6jUKcvnS7tlxMuQB8DBlepiDqnnec0EiEAVmmt9GWlYZ\nJgfGWqDzI1WtouDCIsOhx1Vq7Foue6pgOnibktg2kfYnH9IRAkEA9tKzhlr9rFui\nbVDkiRJK3VTIhKyk4davDjqPJFLJ+4+77wRW164sGxwReD7HtW6qVtJd1MFvqLDO\nqJJEsqDvXQJBAOhvGaWiAPSuP+/z6GE6VXB1pADQFTYIp2DXUa5DcStTGe7hGF1b\nDeAxpDNBbLO3YKYqi2L9vJcIsp5PkHlEVh0CQQCVLIkWBb5VQliryv0knuqiVFCQ\nZyuL1s2cQuYqZOLwaFGERtIZrom3pMImM4NN82F98cyF/pb2lE2CckyUzVF9AkEA\nqhwFjS9Pu8N7j8XWoLHsre2rJd0kaPNUbI+pe/xn6ula5XVgO5LUSOyL2+daAv2G\ngpZIhR5m07LN5wccGWRmEQJBALZRenXaSyX2R2C9ag9r2gaU8/h+aU9he5kjXIt8\n+B8wvpvfOkpOAVCQEMxtsDkEixUtI98YKZP60uw+Xzh40YU=\n-----END RSA PRIVATE KEY-----\n"
|
298
|
+
# }
|
299
|
+
#
|
300
|
+
# Will raise an OpenStack::Exception::BadRequest if an invalid public_key is provided:
|
301
|
+
# >> os.create_keypair({:name=>"marios_keypair_test_invalid", :public_key=>"derp"})
|
302
|
+
# => OpenStack::Exception::BadRequest: Unexpected error while running command.
|
303
|
+
# Stdout: '/tmp/tmp4kI12a/import.pub is not a public key file.\n'
|
304
|
+
#
|
305
|
+
def create_keypair(options)
|
306
|
+
raise OpenStack::Exception::NotImplemented.new("os-keypairs not implemented by #{@connection.http.keys.first}", 501, "NOT IMPLEMENTED") unless api_extensions[:"os-keypairs"]
|
307
|
+
raise OpenStack::Exception::MissingArgument, "Keypair name must be supplied" unless (options[:name])
|
308
|
+
data = JSON.generate(:keypair => options)
|
309
|
+
response = @connection.req("POST", "/os-keypairs", {:data=>data})
|
310
|
+
res = OpenStack.symbolize_keys(JSON.parse(response.body))
|
311
|
+
res[:keypair]
|
312
|
+
end
|
313
|
+
|
314
|
+
# Delete an existing keypair. Raises OpenStack::Exception::NotImplemented
|
315
|
+
# if os-keypairs extension is not implemented (or not advertised) by the OpenStack provider.
|
316
|
+
#
|
317
|
+
# Returns true if succesful.
|
318
|
+
# >> os.delete_keypair("marios_keypair")
|
319
|
+
# => true
|
320
|
+
#
|
321
|
+
# Will raise OpenStack::Exception::ItemNotFound if specified keypair doesn't exist
|
322
|
+
#
|
323
|
+
def delete_keypair(keypair_name)
|
324
|
+
raise OpenStack::Exception::NotImplemented.new("os-keypairs not implemented by #{@connection.http.keys.first}", 501, "NOT IMPLEMENTED") unless api_extensions[:"os-keypairs"]
|
325
|
+
@connection.req("DELETE", "/os-keypairs/#{keypair_name}")
|
326
|
+
true
|
327
|
+
end
|
328
|
+
|
329
|
+
#Security Groups:
|
330
|
+
#Returns a hash with the security group IDs as keys:
|
331
|
+
#=> { "1381" => { :tenant_id=>"12345678909876", :id=>1381, :name=>"default", :description=>"default",
|
332
|
+
# :rules=> [
|
333
|
+
# {:from_port=>22, :group=>{}, :ip_protocol=>"tcp", :to_port=>22,
|
334
|
+
# :parent_group_id=>1381, :ip_range=>{:cidr=>"0.0.0.0/0"}, :id=>4902},
|
335
|
+
# {:from_port=>80, :group=>{}, :ip_protocol=>"tcp", :to_port=>80,
|
336
|
+
# :parent_group_id=>1381, :ip_range=>{:cidr=>"0.0.0.0/0"}, :id=>4903},
|
337
|
+
# {:from_port=>443, :group=>{}, :ip_protocol=>"tcp", :to_port=>443,
|
338
|
+
# :parent_group_id=>1381, :ip_range=>{:cidr=>"0.0.0.0/0"}, :id=>4904},
|
339
|
+
# {:from_port=>-1, :group=>{}, :ip_protocol=>"icmp", :to_port=>-1,
|
340
|
+
# :parent_group_id=>1381, :ip_range=>{:cidr=>"0.0.0.0/0"}, :id=>4905}],
|
341
|
+
# ]
|
342
|
+
# },
|
343
|
+
# "1234" => { ... } }
|
344
|
+
#
|
345
|
+
def security_groups
|
346
|
+
raise OpenStack::Exception::NotImplemented.new("os-security-groups not implemented by #{@connection.http.keys.first}", 501, "NOT IMPLEMENTED") unless api_extensions[:"os-security-groups"] or api_extensions[:security_groups]
|
347
|
+
response = @connection.req("GET", "/os-security-groups")
|
348
|
+
res = OpenStack.symbolize_keys(JSON.parse(response.body))
|
349
|
+
res[:security_groups].inject({}){|result, c| result[c[:id].to_s] = c ; result }
|
350
|
+
end
|
351
|
+
|
352
|
+
def security_group(id)
|
353
|
+
raise OpenStack::Exception::NotImplemented.new("os-security-groups not implemented by #{@connection.http.keys.first}", 501, "NOT IMPLEMENTED") unless api_extensions[:"os-security-groups"] or api_extensions[:security_groups]
|
354
|
+
response = @connection.req("GET", "/os-security-groups/#{id}")
|
355
|
+
res = OpenStack.symbolize_keys(JSON.parse(response.body))
|
356
|
+
{res[:security_group][:id].to_s => res[:security_group]}
|
357
|
+
end
|
358
|
+
|
359
|
+
def create_security_group(name, description)
|
360
|
+
raise OpenStack::Exception::NotImplemented.new("os-security-groups not implemented by #{@connection.http.keys.first}", 501, "NOT IMPLEMENTED") unless api_extensions[:"os-security-groups"] or api_extensions[:security_groups]
|
361
|
+
data = JSON.generate(:security_group => { "name" => name, "description" => description})
|
362
|
+
response = @connection.req("POST", "/os-security-groups", {:data => data})
|
363
|
+
res = OpenStack.symbolize_keys(JSON.parse(response.body))
|
364
|
+
{res[:security_group][:id].to_s => res[:security_group]}
|
365
|
+
end
|
366
|
+
|
367
|
+
def delete_security_group(id)
|
368
|
+
raise OpenStack::Exception::NotImplemented.new("os-security-groups not implemented by #{@connection.http.keys.first}", 501, "NOT IMPLEMENTED") unless api_extensions[:"os-security-groups"] or api_extensions[:security_groups]
|
369
|
+
response = @connection.req("DELETE", "/os-security-groups/#{id}")
|
370
|
+
true
|
371
|
+
end
|
372
|
+
|
373
|
+
#params: { :ip_protocol=>"tcp", :from_port=>"123", :to_port=>"123", :cidr=>"192.168.0.1/16", :group_id:="123" }
|
374
|
+
#observed behaviour against Openstack@HP cloud - can specify either cidr OR group_id as source, but not both
|
375
|
+
#if both specified, the group is used and the cidr ignored.
|
376
|
+
def create_security_group_rule(security_group_id, params)
|
377
|
+
raise OpenStack::Exception::NotImplemented.new("os-security-groups not implemented by #{@connection.http.keys.first}", 501, "NOT IMPLEMENTED") unless api_extensions[:"os-security-groups"] or api_extensions[:security_groups]
|
378
|
+
params.merge!({:parent_group_id=>security_group_id.to_s})
|
379
|
+
data = JSON.generate(:security_group_rule => params)
|
380
|
+
response = @connection.req("POST", "/os-security-group-rules", {:data => data})
|
381
|
+
res = OpenStack.symbolize_keys(JSON.parse(response.body))
|
382
|
+
{res[:security_group_rule][:id].to_s => res[:security_group_rule]}
|
383
|
+
end
|
384
|
+
|
385
|
+
def delete_security_group_rule(id)
|
386
|
+
raise OpenStack::Exception::NotImplemented.new("os-security-groups not implemented by #{@connection.http.keys.first}", 501, "NOT IMPLEMENTED") unless api_extensions[:"os-security-groups"] or api_extensions[:security_groups]
|
387
|
+
response = @connection.req("DELETE", "/os-security-group-rules/#{id}")
|
388
|
+
true
|
389
|
+
end
|
390
|
+
|
391
|
+
#VOLUMES - attach detach
|
392
|
+
def attach_volume(server_id, volume_id, device_id)
|
393
|
+
raise OpenStack::Exception::NotImplemented.new("os-volumes not implemented by #{@connection.http.keys.first}", 501, "NOT IMPLEMENTED") unless api_extensions[:"os-volumes"]
|
394
|
+
data = JSON.generate(:volumeAttachment => {"volumeId" => volume_id, "device"=> device_id})
|
395
|
+
response = @connection.req("POST", "/servers/#{server_id}/os-volume_attachments", {:data=>data})
|
396
|
+
true
|
397
|
+
end
|
398
|
+
|
399
|
+
def list_attachments(server_id)
|
400
|
+
raise OpenStack::Exception::NotImplemented.new("os-volumes not implemented by #{@connection.http.keys.first}", 501, "NOT IMPLEMENTED") unless api_extensions[:"os-volumes"]
|
401
|
+
response = @connection.req("GET", "/servers/#{server_id}/os-volume_attachments")
|
402
|
+
OpenStack.symbolize_keys(JSON.parse(response.body))
|
403
|
+
end
|
404
|
+
|
405
|
+
def detach_volume(server_id, attachment_id)
|
406
|
+
raise OpenStack::Exception::NotImplemented.new("os-volumes not implemented by #{@connection.http.keys.first}", 501, "NOT IMPLEMENTED") unless api_extensions[:"os-volumes"]
|
407
|
+
response = @connection.req("DELETE", "/servers/#{server_id}/os-volume_attachments/#{attachment_id}")
|
408
|
+
true
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|