cft_smartcloud 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/.gitignore +2 -0
  2. data/CHANGELOG +8 -0
  3. data/LICENSE +178 -0
  4. data/README.rdoc +71 -0
  5. data/Rakefile +53 -0
  6. data/VERSION +1 -0
  7. data/bin/smartcloud +37 -0
  8. data/cft_smartcloud.gemspec +176 -0
  9. data/lib/cli_tools/README.txt +50 -0
  10. data/lib/cli_tools/ic-add-keypair.cmd +29 -0
  11. data/lib/cli_tools/ic-add-keypair.sh +15 -0
  12. data/lib/cli_tools/ic-allocate-address.cmd +29 -0
  13. data/lib/cli_tools/ic-allocate-address.sh +14 -0
  14. data/lib/cli_tools/ic-attach-volume.cmd +27 -0
  15. data/lib/cli_tools/ic-attach-volume.sh +27 -0
  16. data/lib/cli_tools/ic-clone-image.cmd +27 -0
  17. data/lib/cli_tools/ic-clone-image.sh +14 -0
  18. data/lib/cli_tools/ic-clone-volume.cmd +27 -0
  19. data/lib/cli_tools/ic-clone-volume.sh +60 -0
  20. data/lib/cli_tools/ic-cmd.cmd +41 -0
  21. data/lib/cli_tools/ic-cmd.sh +38 -0
  22. data/lib/cli_tools/ic-create-instance.cmd +27 -0
  23. data/lib/cli_tools/ic-create-instance.sh +14 -0
  24. data/lib/cli_tools/ic-create-password.cmd +27 -0
  25. data/lib/cli_tools/ic-create-password.sh +14 -0
  26. data/lib/cli_tools/ic-create-volume.cmd +27 -0
  27. data/lib/cli_tools/ic-create-volume.sh +14 -0
  28. data/lib/cli_tools/ic-delete-image.cmd +27 -0
  29. data/lib/cli_tools/ic-delete-image.sh +14 -0
  30. data/lib/cli_tools/ic-delete-instance.cmd +27 -0
  31. data/lib/cli_tools/ic-delete-instance.sh +14 -0
  32. data/lib/cli_tools/ic-delete-volume.cmd +27 -0
  33. data/lib/cli_tools/ic-delete-volume.sh +14 -0
  34. data/lib/cli_tools/ic-describe-address-offerings.cmd +27 -0
  35. data/lib/cli_tools/ic-describe-address-offerings.sh +14 -0
  36. data/lib/cli_tools/ic-describe-addresses.cmd +27 -0
  37. data/lib/cli_tools/ic-describe-addresses.sh +14 -0
  38. data/lib/cli_tools/ic-describe-image-agreement.cmd +27 -0
  39. data/lib/cli_tools/ic-describe-image-agreement.sh +14 -0
  40. data/lib/cli_tools/ic-describe-image.cmd +27 -0
  41. data/lib/cli_tools/ic-describe-image.sh +14 -0
  42. data/lib/cli_tools/ic-describe-images.cmd +27 -0
  43. data/lib/cli_tools/ic-describe-images.sh +14 -0
  44. data/lib/cli_tools/ic-describe-instance.cmd +27 -0
  45. data/lib/cli_tools/ic-describe-instance.sh +14 -0
  46. data/lib/cli_tools/ic-describe-instances.cmd +27 -0
  47. data/lib/cli_tools/ic-describe-instances.sh +14 -0
  48. data/lib/cli_tools/ic-describe-keypair.cmd +27 -0
  49. data/lib/cli_tools/ic-describe-keypair.sh +14 -0
  50. data/lib/cli_tools/ic-describe-keypairs.cmd +27 -0
  51. data/lib/cli_tools/ic-describe-keypairs.sh +14 -0
  52. data/lib/cli_tools/ic-describe-location.cmd +14 -0
  53. data/lib/cli_tools/ic-describe-location.sh +14 -0
  54. data/lib/cli_tools/ic-describe-locations.cmd +14 -0
  55. data/lib/cli_tools/ic-describe-locations.sh +14 -0
  56. data/lib/cli_tools/ic-describe-request.cmd +27 -0
  57. data/lib/cli_tools/ic-describe-request.sh +14 -0
  58. data/lib/cli_tools/ic-describe-vlans.cmd +27 -0
  59. data/lib/cli_tools/ic-describe-vlans.sh +14 -0
  60. data/lib/cli_tools/ic-describe-volume-offerings.cmd +27 -0
  61. data/lib/cli_tools/ic-describe-volume-offerings.sh +14 -0
  62. data/lib/cli_tools/ic-describe-volume.cmd +27 -0
  63. data/lib/cli_tools/ic-describe-volume.sh +14 -0
  64. data/lib/cli_tools/ic-describe-volumes.cmd +27 -0
  65. data/lib/cli_tools/ic-describe-volumes.sh +14 -0
  66. data/lib/cli_tools/ic-detach-volume.cmd +27 -0
  67. data/lib/cli_tools/ic-detach-volume.sh +27 -0
  68. data/lib/cli_tools/ic-extend-reservation.cmd +27 -0
  69. data/lib/cli_tools/ic-extend-reservation.sh +14 -0
  70. data/lib/cli_tools/ic-generate-keypair.cmd +27 -0
  71. data/lib/cli_tools/ic-generate-keypair.sh +14 -0
  72. data/lib/cli_tools/ic-release-address.cmd +27 -0
  73. data/lib/cli_tools/ic-release-address.sh +14 -0
  74. data/lib/cli_tools/ic-remove-keypair.cmd +27 -0
  75. data/lib/cli_tools/ic-remove-keypair.sh +14 -0
  76. data/lib/cli_tools/ic-restart-instance.cmd +27 -0
  77. data/lib/cli_tools/ic-restart-instance.sh +14 -0
  78. data/lib/cli_tools/ic-save-instance.cmd +27 -0
  79. data/lib/cli_tools/ic-save-instance.sh +14 -0
  80. data/lib/cli_tools/ic-set-default-key.cmd +27 -0
  81. data/lib/cli_tools/ic-set-default-key.sh +14 -0
  82. data/lib/cli_tools/ic-update-instance.cmd +27 -0
  83. data/lib/cli_tools/ic-update-instance.sh +14 -0
  84. data/lib/cli_tools/ic-update-keypair.cmd +27 -0
  85. data/lib/cli_tools/ic-update-keypair.sh +14 -0
  86. data/lib/cli_tools/lib/DeveloperCloud_API_Client_JAR.jar +0 -0
  87. data/lib/cli_tools/lib/DeveloperCloud_CMD_Tool.jar +0 -0
  88. data/lib/cli_tools/lib/commons-beanutils-1.6.1.jar +0 -0
  89. data/lib/cli_tools/lib/commons-cli-1.2.jar +0 -0
  90. data/lib/cli_tools/lib/commons-codec-1.3.jar +0 -0
  91. data/lib/cli_tools/lib/commons-collections-3.2.1.jar +0 -0
  92. data/lib/cli_tools/lib/commons-digester-1.8.jar +0 -0
  93. data/lib/cli_tools/lib/commons-httpclient-3.1.jar +0 -0
  94. data/lib/cli_tools/lib/commons-lang-2.3.jar +0 -0
  95. data/lib/cli_tools/lib/commons-logging-1.1.1.jar +0 -0
  96. data/lib/cli_tools/logging.properties +7 -0
  97. data/lib/cli_tools/manifest.rmd +26 -0
  98. data/lib/config/config.yml +50 -0
  99. data/lib/hash_fix.rb +37 -0
  100. data/lib/mime-types-1.16/History.txt +107 -0
  101. data/lib/mime-types-1.16/Install.txt +17 -0
  102. data/lib/mime-types-1.16/Licence.txt +15 -0
  103. data/lib/mime-types-1.16/Manifest.txt +12 -0
  104. data/lib/mime-types-1.16/README.txt +28 -0
  105. data/lib/mime-types-1.16/Rakefile +316 -0
  106. data/lib/mime-types-1.16/lib/mime/types.rb +751 -0
  107. data/lib/mime-types-1.16/lib/mime/types.rb.data +1324 -0
  108. data/lib/mime-types-1.16/mime-types.gemspec +43 -0
  109. data/lib/mime-types-1.16/setup.rb +1585 -0
  110. data/lib/mime-types-1.16/test/test_mime_type.rb +356 -0
  111. data/lib/mime-types-1.16/test/test_mime_types.rb +122 -0
  112. data/lib/mock_smartcloud.rb +53 -0
  113. data/lib/rest-client-1.6.3/README.rdoc +276 -0
  114. data/lib/rest-client-1.6.3/Rakefile +66 -0
  115. data/lib/rest-client-1.6.3/VERSION +1 -0
  116. data/lib/rest-client-1.6.3/bin/restclient +92 -0
  117. data/lib/rest-client-1.6.3/history.md +112 -0
  118. data/lib/rest-client-1.6.3/lib/rest-client.rb +2 -0
  119. data/lib/rest-client-1.6.3/lib/rest_client.rb +2 -0
  120. data/lib/rest-client-1.6.3/lib/restclient/abstract_response.rb +106 -0
  121. data/lib/rest-client-1.6.3/lib/restclient/exceptions.rb +193 -0
  122. data/lib/rest-client-1.6.3/lib/restclient/net_http_ext.rb +21 -0
  123. data/lib/rest-client-1.6.3/lib/restclient/payload.rb +220 -0
  124. data/lib/rest-client-1.6.3/lib/restclient/raw_response.rb +34 -0
  125. data/lib/rest-client-1.6.3/lib/restclient/request.rb +314 -0
  126. data/lib/rest-client-1.6.3/lib/restclient/resource.rb +169 -0
  127. data/lib/rest-client-1.6.3/lib/restclient/response.rb +24 -0
  128. data/lib/rest-client-1.6.3/lib/restclient.rb +174 -0
  129. data/lib/restclient_fix.rb +41 -0
  130. data/lib/smartcloud.rb +616 -0
  131. data/lib/smartcloud_logger.rb +20 -0
  132. data/lib/xml-simple-1.0.12/lib/xmlsimple.rb +1028 -0
  133. data/script/console +3 -0
  134. data/test/helper.rb +22 -0
  135. metadata +196 -0
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ #####################################################################################
3
+ # Copyright (c) 2011, Cohesive Flexible Technologies, Inc.
4
+ # This copyrighted material is the property of Cohesive Flexible Technologies and
5
+ # is subject to the license terms of the product it is contained within, whether
6
+ # in text or compiled form. It is licensed under the terms expressed in the
7
+ # accompanying README and LICENSE files.
8
+ #
9
+ # This program is AS IS and WITHOUT ANY WARRANTY; without even the implied warranty
10
+ # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+ #####################################################################################
12
+
13
+ # Enables modification of timeout in RestClient
14
+ module RestClient
15
+
16
+ class << self
17
+ attr_accessor :timeout
18
+ end
19
+
20
+ def self.get(url, headers={}, &block)
21
+ Request.execute(:method => :get, :url => url, :headers => headers, :timeout => @timeout, &block)
22
+ end
23
+
24
+ def self.post(url, payload, headers={}, &block)
25
+ Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers, :timeout => @timeout, &block)
26
+ end
27
+
28
+ def self.patch(url, payload, headers={}, &block)
29
+ Request.execute(:method => :patch, :url => url, :payload => payload, :headers => headers, :timeout => @timeout, &block)
30
+ end
31
+
32
+ def self.put(url, payload, headers={}, &block)
33
+ Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers, :timeout => @timeout, &block)
34
+ end
35
+
36
+ def self.delete(url, headers={}, &block)
37
+ Request.execute(:method => :delete, :url => url, :headers => headers, :timeout => @timeout, &block)
38
+ end
39
+
40
+ end
41
+
data/lib/smartcloud.rb ADDED
@@ -0,0 +1,616 @@
1
+ #!/usr/bin/env ruby
2
+ #####################################################################################
3
+ # Copyright (c) 2011, Cohesive Flexible Technologies, Inc.
4
+ # This copyrighted material is the property of Cohesive Flexible Technologies and
5
+ # is subject to the license terms of the product it is contained within, whether
6
+ # in text or compiled form. It is licensed under the terms expressed in the
7
+ # accompanying README and LICENSE files.
8
+ #
9
+ # This program is AS IS and WITHOUT ANY WARRANTY; without even the implied warranty
10
+ # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+ #####################################################################################
12
+
13
+ Dir["#{File.dirname(__FILE__)}/**/**"].each {|dir| $LOAD_PATH << dir }
14
+ $LOAD_PATH << File.dirname(__FILE__)
15
+
16
+ require 'tempfile'
17
+ require 'logger'
18
+ require 'cgi'
19
+ require 'rest_client'
20
+ require 'yaml'
21
+ require 'restclient_fix'
22
+ require 'hash_fix'
23
+ require 'xmlsimple'
24
+ require 'smartcloud_logger'
25
+
26
+ IBM_TOOLS_HOME=File.join(File.dirname(__FILE__), "cli_tools") unless defined?(IBM_TOOLS_HOME)
27
+
28
+ # Encapsulates communications with IBM SmartCloud via REST
29
+ class IBMSmartCloud
30
+
31
+ attr_accessor :logger
32
+
33
+ def initialize(username, password, logger=nil, debug=false)
34
+ @username = username
35
+ @password = password
36
+ @logger = logger || SmartcloudLogger.new(STDOUT)
37
+ @debug = debug
38
+
39
+ @config = YAML.load_file(File.join(File.dirname(__FILE__), "config/config.yml"))
40
+ @states = @config["states"]
41
+
42
+ @api_url = @config["api_url"]
43
+ @api_url.gsub!("https://", "https://#{CGI::escape(username)}:#{CGI::escape(password)}@")
44
+
45
+ RestClient.timeout = 120 # ibm requests can be very slow
46
+ RestClient.log = @logger if @debug
47
+ end
48
+
49
+ class << self
50
+ @config = YAML.load_file(File.join(File.dirname(__FILE__), "config/config.yml"))
51
+ attr_reader :method_help
52
+ attr_reader :config
53
+ end
54
+
55
+ def self.args(method, args)
56
+ @method_help||={}
57
+ @method_help[method.to_s] = args
58
+ end
59
+
60
+ def help(method=nil)
61
+ if method
62
+ args = (self.class.method_help[method.to_s])
63
+ if args.nil?
64
+ puts method.to_s
65
+ else
66
+ args = args.map do |arg|
67
+ if arg.is_a?(Hash)
68
+ # If an argument is required, just list it
69
+ if arg.values.first==:req
70
+ arg.keys.first.to_s
71
+ # If it's optional, list it in brackets
72
+ elsif arg.values.first==:opt
73
+ "[#{arg.keys.first.to_s}]"
74
+ # If there is an array of options, list them
75
+ else
76
+ "#{arg.keys.first.to_s}=>#{arg.values.first.inspect}"
77
+ end
78
+ else
79
+ arg
80
+ end
81
+ end.join(", ")
82
+
83
+ puts "#{method.to_s}(#{args})"
84
+ end
85
+ else
86
+ methods = public_methods - Object.public_methods - ['post','get','put','delete','logger','logger=','help']
87
+ puts methods.sort.join("\n")
88
+ end
89
+ end
90
+
91
+ # Get a list of data centers
92
+ def describe_locations
93
+ get("/locations").Location
94
+ end
95
+
96
+ def display_locations
97
+ log = describe_locations.map {|loc| "#{loc.ID} #{loc.Location}"}.join("\n")
98
+ logger.info "\n#{log}"
99
+ end
100
+
101
+ # Create an instance. The instance_params hash can follow
102
+ # CLI-style attribute naming. I.e. "image-id" or "data-center"
103
+ # or API-style (imageID, location). If the CLI-style params are
104
+ # provided, they will be remapped to the correct API params.
105
+ args :create_instance, [:instance_params_hash]
106
+ def create_instance(instance_params)
107
+ param_remap = { "name" => "name",
108
+ "image-id" => "imageID",
109
+ "instance-type" => "instanceType",
110
+ "data-center" => "location",
111
+ "key-name" => "publicKey",
112
+ "ip-address-id" => "ip",
113
+ "volume-id" => "volumeID",
114
+ "configuration" => "ConfigurationData",
115
+ "vlan-id" => "vlanID",
116
+ "antiCollocationInstance" => "antiCollocationInstance",
117
+ "isMiniEphemeral" => "isMiniEphemeral"
118
+ }
119
+
120
+ # api does not take description
121
+ instance_params.delete("description")
122
+
123
+ # configuration data has to be changed from a string like
124
+ # <configuration>{contextmanager:baml-c3-master.cohesiveft.com,clustername:BAML_poc_pk0515,role:[nfs-client-setup|newyork_master_refdata_member|install-luci|rhel-openlikewise-client-setup|join-domain],hostname:r550n107}</configuration>
125
+ # to a standard list of POST params like
126
+ # contextmanager=baml-c3-mager&clustername=BAML...
127
+ configuration_data = instance_params.delete("configuration") || instance_params.delete("ConfigurationData")
128
+ if configuration_data
129
+ if configuration_data =~ /\s+/
130
+ logger.warn "<configuration> tag should not contain spaces! Correct format looks like: <configuration>{foo:bar,baz:quux}</configuration>. Spaces will be removed."
131
+ end
132
+ configuration_data.delete!("{}") # get rid of curly braces
133
+ config_data_params = configuration_data.split(",").map{|param| param.split(":")} # split each foo:bar into key and value
134
+ config_data_params.each {|k,v| instance_params[k]=v} # add it into the standard instance params array
135
+ end
136
+
137
+ post "/instances", instance_params, param_remap
138
+ end
139
+
140
+ # Find a location by its name. Assumes locations have unique names (not verified by ibm docs)
141
+ args :get_locations_by_name, [:name]
142
+ def get_location_by_name(name)
143
+ locations = describe_locations
144
+ locations.detect {|loc| loc.Name == name}
145
+ end
146
+
147
+ # find all ip address offerings. If a location is specified, give the offering for the location
148
+ # assumption: only one offering per location. This is assumed, not verified in docs.
149
+ args :describe_address_offerings, [{:location => :opt}]
150
+ def describe_address_offerings(location=nil)
151
+ response=get("/offerings/address").Offerings
152
+ if location
153
+ response.detect {|offering| offering.Location.to_s == location.to_s}
154
+ else
155
+ response
156
+ end
157
+ end
158
+
159
+ # Allocate IP address. Offering ID is optional. if not specified, will look up offerings
160
+ # for this location and pick one
161
+ args :allocate_address, [{:location => :req}, {:offering_id => :opt}, {:foo => [:bar, :baz, :quux]}]
162
+ def allocate_address(location, offering_id=nil)
163
+ if offering_id.nil?
164
+ offering_id = describe_address_offerings(location).ID
165
+ end
166
+ post("/addresses", :offeringID => offering_id, :location => location).Address
167
+ end
168
+
169
+
170
+ args :describe_address, [:address_id]
171
+ def describe_address(address_id)
172
+ address = get("/addresses/#{address_id}").Address
173
+ address["State"] = @config.states.ip[address.State]
174
+ address
175
+ end
176
+
177
+ # Launches a clone image call from CLI
178
+ args :clone_image, [:name, :description, :image_id]
179
+ def clone_image(name, description, image_id)
180
+ post("/offerings/image/#{image_id}", :name => name, :description => description).ImageID
181
+ end
182
+
183
+ # Launches a clone request and returns ID of new volume
184
+ # TODO: the API call does not work, we have to use the cli for now
185
+ args :clone_volume, [:name, :source_disk_id]
186
+ def clone_volume(name, source_disk_id)
187
+ # result = post("/storage", :name => name, :SourceDiskID => source_disk_id)
188
+ # result.Volume.ID
189
+ create_password_file
190
+ result = run_ibm_tool("ic-clone-volume.sh -n #{name} -S #{source_disk_id}")
191
+ result =~ /ID : (.*)/
192
+ return $1 # grab the id of the created volume
193
+ end
194
+
195
+ # Delete the volume
196
+ args :delete_volume, [:vol_id]
197
+ def delete_volume(vol_id)
198
+ delete("/storage/#{vol_id}")
199
+ true
200
+ end
201
+
202
+ # generates a keypair and returns the private key
203
+ args :generate_keypair, [:name]
204
+ def generate_keypair(name)
205
+ response = post("/keys", :name => name)
206
+ response.PrivateKey.KeyMaterial
207
+ end
208
+
209
+ args :remove_keypair, [:name]
210
+ def remove_keypair(name)
211
+ delete("/keys/#{name}")
212
+ true
213
+ end
214
+
215
+ args :describe_key, [:name]
216
+ def describe_key(name)
217
+ get("/keys/#{name}").PublicKey
218
+ end
219
+
220
+ # If address_id is supplied, will return info on that address only
221
+ args :describe_addresses, [{:address_id=>:opt}]
222
+ def describe_addresses(address_id=nil)
223
+ response = get("/addresses").Address
224
+ response.each do |a|
225
+ a["State"] = @states["ip"][a.State.to_i]
226
+ end
227
+ if address_id
228
+ response = response.detect {|address| address.ID.to_s == address_id.to_s}
229
+ end
230
+ response
231
+ end
232
+
233
+ # Allows you to poll for a specific storage state
234
+ # ex: storage_state_is?("123456", "UNMOUNTED")
235
+ # storage state string can be given as a string or symbol
236
+ args :describe_addresses, [{:address_id=>:req}, {:state_string=>%w(new attached etc...)}]
237
+ def address_state_is?(address_id, state_string)
238
+ v = describe_addresses(address_id)
239
+ v.State.to_s == state_string.to_s.upcase
240
+ end
241
+
242
+ def describe_keys
243
+ get("/keys").PublicKey
244
+ end
245
+
246
+ def describe_unused_keys
247
+ describe_keys.select {|key| key.Instances == {}}
248
+ end
249
+
250
+ def delete_unused_keys(auto=false)
251
+ describe_unused_keys.each do |key|
252
+ if auto
253
+ remove_keypair(key.KeyName)
254
+ else
255
+ puts "Remove key #{key.KeyName}? (type 'y' to continue): "
256
+ input = gets.strip
257
+ if input == 'y'
258
+ remove_keypair(key.KeyName)
259
+ else
260
+ puts "Not removing key #{key.KeyName}"
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ def display_keys
267
+ logger.info "\nKeyName".ljust(50) + " | Instance ID's\n" + describe_keys.map {|key| key.KeyName.strip.ljust(50) + " | " + (key.Instances.empty? ? '[]' : key.Instances.InstanceID.inspect )}.join("\n")
268
+ end
269
+
270
+ args :supported_instance_types, [:image_id]
271
+ def supported_instance_types(image_id)
272
+ img=describe_image(image_id)
273
+ arrayize(img.SupportedInstanceTypes.InstanceType).map(&:ID)
274
+ end
275
+ # NOTE: doesn't seem to be supported
276
+ # def describe_vlans
277
+ # get("/offerings/vlan").Vlan
278
+ # end
279
+
280
+ # Optionally supply a location to get offerings filtered to a particular location
281
+ # Optionally supply a name (Small, Medium, Large) to get the specific offering
282
+ args :describe_volume_offerings, [{:location => :opt}, {:name => :opt}]
283
+ def describe_volume_offerings(location=nil, name=nil)
284
+ response = get("/offerings/storage")
285
+ if location
286
+ filtered_by_location = response.Offerings.select {|o| o.Location.to_s == location.to_s}
287
+ if name
288
+ filtered_by_location.detect {|o| o.Name == name}
289
+ else
290
+ filtered_by_location
291
+ end
292
+ else
293
+ response.Offerings
294
+ end
295
+ end
296
+
297
+ # Create a volume. offering_id is optional and will be determined automatically
298
+ # for you based on the location and volume size.
299
+ #
300
+ # ex: create_volume("yan", 61, "Small", "20001208", 61)
301
+ #
302
+ # NOTE: storage area id is not currently supported by IBM (according to docs)
303
+ args :create_volume, [{:name => :req},{:location_id => :req},{:size => ['Small','Medium','Large']}, {:offering_id => :opt}, {:format => :opt}]
304
+ def create_volume(name, location, size, offering_id=nil, format="EXT3")
305
+
306
+ # figure out the offering ID automatically based on location and size
307
+ if offering_id.nil?
308
+ logger.debug "Looking up volume offerings based on location: #{location} and size: #{size}"
309
+ offering_id = describe_volume_offerings(location, size).ID
310
+ end
311
+
312
+ logger.debug "Creating volume...please wait."
313
+ result = post("/storage", :format => format, :location => location, :name => name, :size => size, :offeringID => offering_id)
314
+ result.Volume.ID
315
+ end
316
+
317
+ # Optionally takes a volume id, if none supplied, will show all volumes
318
+ args :describe_storage, [{:volume_id => :opt}]
319
+ def describe_storage(volume_id=nil)
320
+ response = volume_id ? get("/storage/#{volume_id}") : get("/storage")
321
+
322
+ if response.Volume.is_a?(Array)
323
+ response.Volume.each do |v|
324
+ v["State"] = @states["storage"][v.State.to_i]
325
+ end
326
+ elsif response.Volume
327
+ response.Volume["State"] = @states["storage"][response.Volume.State.to_i]
328
+ else
329
+ return []
330
+ end
331
+
332
+ response.Volume
333
+ end
334
+
335
+ alias describe_volumes describe_storage
336
+ alias describe_volume describe_storage
337
+
338
+ # This does a human-usable version of describe_volumes with the critical info (name, id, state)
339
+ # Optionally takes a filter (currently supports state). i.e. display_volumes(:state => :mounted)
340
+ args :display_volumes, [{:filter => :opt}]
341
+ def display_volumes(filter={})
342
+ vols = describe_volumes
343
+ vols = filter_and_sort(vols, filter)
344
+
345
+ log = "\nVolume | State | Loc | Name\n"
346
+ volsz = vols.map {|vol| vol.ID.ljust(6) + " | " + (vol.State[0..9].ljust(10) rescue '?'.ljust(10)) + " | " + vol.Location.ljust(4) + " | " + vol.Name }.join("\n")
347
+ log << volsz
348
+ logger.info log
349
+ true
350
+ end
351
+
352
+ alias display_storage display_volumes
353
+
354
+ # Allows you to poll for a specific storage state
355
+ # ex: storage_state_is?("123456", "UNMOUNTED")
356
+ # storage state string can be given as a string or symbol
357
+ args :storage_state_is?, [{:volume_id => :req}, {:state_string => %w(mounted unmounted etc...)}]
358
+ def storage_state_is?(volume_id, state_string)
359
+ v = describe_storage(volume_id)
360
+
361
+ @last_storage_state||={}
362
+ if @last_storage_state[volume_id.to_s] != v.State
363
+ logger.debug "Volume: #{volume_id}; Current State: #{v.State}; Waiting for: #{state_string.to_s.upcase} " # log it every time it changes
364
+ end
365
+ @last_storage_state[volume_id.to_s] = v.State
366
+
367
+ if v.State.to_s == state_string.to_s.upcase
368
+ v
369
+ else
370
+ false
371
+ end
372
+
373
+ end
374
+
375
+ args :instance_state_is?, [{:instance_id=> :req}, {:state_string => %w(active stopping etc...)}]
376
+ def instance_state_is?(instance_id, state_string)
377
+ v = describe_instance(instance_id)
378
+
379
+ @last_instance_state||={}
380
+ if @last_instance_state[instance_id.to_s] != v.Status
381
+ logger.debug "Instance: #{instance_id}; Current State: #{v.Status}; Waiting for: #{state_string.to_s.upcase}" # log it every time it changes
382
+ end
383
+ @last_instance_state[instance_id.to_s] = v.Status
384
+
385
+ if v.Status.to_s == state_string.to_s.upcase
386
+ v
387
+ else
388
+ false
389
+ end
390
+ end
391
+
392
+ # Polls until volume state is matched. When it is, returns entire volume descriptor hash.
393
+ args :poll_for_volume_state, [{:volume_id => :req}, {:state_string => %w(mounted unmounted etc...)}]
394
+ def poll_for_volume_state(volume_id, state_string, polling_interval=5)
395
+ logger.debug "Polling for volume #{volume_id} to acquire state #{state_string} (interval: #{polling_interval})..."
396
+ while(true)
397
+ descriptor = storage_state_is?(volume_id, state_string)
398
+ return descriptor if descriptor
399
+ sleep(polling_interval)
400
+ end
401
+ end
402
+
403
+ # Polls until instance state is matched. When it is, returns entire instance descriptor hash.
404
+ args :poll_for_instance_state, [{:instance_id => :req}, {:state_string => %w(active stopped etc...)}]
405
+ def poll_for_instance_state(instance_id, state_string, polling_interval=5)
406
+ logger.debug "Polling for instance #{instance_id} to acquire state #{state_string} (interval: #{polling_interval})..."
407
+ while(true)
408
+ descriptor = instance_state_is?(instance_id, state_string)
409
+ return descriptor if descriptor
410
+ sleep(polling_interval)
411
+ end
412
+ end
413
+
414
+ # Deletes many instances in a thread
415
+ args :delete_instances, [:instance_ids => [12345,12346,12347, '...']]
416
+ def delete_instances(instance_ids)
417
+ instance_ids.each {|id| delete_instance(id) }
418
+ end
419
+
420
+
421
+ args :delete_instance, [:instance_id]
422
+ def delete_instance(instance_id)
423
+ delete("/instances/#{instance_id}")
424
+ true
425
+ end
426
+
427
+ args :restart_instance, [:instance_id]
428
+ def restart_instance(instance_id)
429
+ put("/instances/#{instance_id}", :state => "restart")
430
+ true
431
+ end
432
+
433
+ args :describe_instance, [:instance_id]
434
+ def describe_instance(instance_id)
435
+ response = get("instances/#{instance_id}").Instance
436
+ response["Status"] = @states["instance"][response.Status.to_i]
437
+ response
438
+ end
439
+
440
+ # You can filter by any instance attributes such as
441
+ # describe_instances(:name => "FOO_BAR", :status => 'ACTIVE')
442
+ # in the case of status you can also use symbols like :status => :active
443
+ args :describe_instances, [:filters => :opt]
444
+ def describe_instances(filters={})
445
+ instances = arrayize(get("instances").Instance)
446
+
447
+ instances.each do |instance|
448
+ instance["Status"] = @states["instance"][instance.Status.to_i]
449
+ end
450
+
451
+ filters[:order] ||= "LaunchTime"
452
+ instances = filter_and_sort(instances, filters)
453
+ end
454
+
455
+ # Same as describe_instances except prints a human readable summary
456
+ # Also takes an :order param, examples:
457
+ # display_instances(:order => "Name") or :order => "LaunchTime"
458
+ #
459
+ args :display_instances, [:filters => :opt]
460
+ def display_instances(filters={})
461
+ instances = describe_instances(filters)
462
+
463
+ log = %{#{"Started".ljust(18)} | #{"Instance".ljust(8)} | #{"Image".ljust(9)} | #{"Loc".ljust(3)} | #{"Status".ljust(10)} | #{"KeyName".ljust(15)} | #{"IP".ljust(15)} | Name\n}
464
+ log << instances.map do |ins|
465
+ "#{DateTime.parse(ins.LaunchTime).strftime("%Y-%m-%d %I:%M%p")} | #{ins.ID.ljust(8)} | #{ins.ImageID.ljust(9)} | #{ins.Location.ljust(3)} | #{ins.Status[0..9].ljust(10)} | #{(ins.KeyName || "").strip[0..14].ljust(15)} | #{(ins.IP.strip=="" ? '[NONE]' : ins.IP.strip).to_s.ljust(15)} | #{ins.Name}"
466
+ end.join("\n")
467
+ logger.info "\n#{log}"
468
+ end
469
+
470
+
471
+ args :describe_image, [:image_id]
472
+ def describe_image(image_id)
473
+ image = get("offerings/image/#{image_id}").Image
474
+ image["State"] = @states["image"][image.State.to_i]
475
+ image
476
+ end
477
+
478
+ args :describe_images, [:filters => :opt]
479
+ def describe_images(filters={})
480
+ images = arrayize(get("offerings/image/").Image)
481
+ images.each {|img| img["State"] = @states["image"][img.State.to_i]}
482
+ filters[:order] ||= "Location"
483
+ images = filter_and_sort(images, filters)
484
+ end
485
+
486
+ args :display_images, [:filters => :opt]
487
+ def display_images(filters={})
488
+ images = describe_images(filters)
489
+
490
+ log = images.map do |i|
491
+ types = arrayize(i.SupportedInstanceTypes.InstanceType).map(&:ID).join(", ") rescue "[INSTANCE TYPE UNKNOWN]"
492
+ "#{i.ID} | #{i.Location} | #{i.Name} | #{types}"
493
+ end.join("\n")
494
+ logger.info "\n#{log}"
495
+ end
496
+
497
+ def delete(path)
498
+ output = RestClient.delete File.join(@api_url, path), :accept => :response
499
+ response = XmlSimple.xml_in(output, {'ForceArray' => nil})
500
+ rescue => e
501
+ raise_restclient_error(e)
502
+ end
503
+
504
+ def put(path, params={}, param_remap={})
505
+ param_string = make_param_string(params, param_remap)
506
+ output = RestClient.put File.join(@api_url, path), param_string, :accept => :response
507
+ response = XmlSimple.xml_in(output, {'ForceArray' => nil})
508
+ rescue => e
509
+ raise_restclient_error(e)
510
+ end
511
+
512
+ def get(path)
513
+ output = RestClient.get File.join(@api_url, path), :accept => :response
514
+ response = XmlSimple.xml_in(output, {'ForceArray' => nil})
515
+ rescue => e
516
+ raise_restclient_error(e)
517
+ end
518
+
519
+ def post(path, params={}, param_remap=nil)
520
+ param_string = make_param_string(params, param_remap)
521
+ output = RestClient.post File.join(@api_url, path), param_string, :accept => :response
522
+ response = XmlSimple.xml_in(output, {'ForceArray' => nil})
523
+ response
524
+ rescue => e
525
+ raise_restclient_error(e)
526
+ end
527
+
528
+ private
529
+ # rest client error details are in the response so we want to
530
+ # display that as the error, otherwise we lose that info
531
+ def raise_restclient_error(e)
532
+ if e.respond_to?(:response) && !e.is_a?(RestClient::RequestTimeout)
533
+ raise "#{e.message} - #{e.response}"
534
+ else
535
+ raise e
536
+ end
537
+ end
538
+
539
+ def make_param_string(params, param_remap)
540
+ param_string = params.map do |k,v|
541
+ k=k.to_s # symbol keys turn to string
542
+
543
+ # logger.debug "Removing all spaces from parameters, smartcloud API does not allow spaces."
544
+ # v = v.gsub(/\\s+/,'') # remove all spaces! smartcloud does not like spaces in params
545
+ if param_remap && param_remap[k]
546
+ k = param_remap[k]
547
+ end
548
+
549
+ "#{CGI.escape(k)}=#{CGI.escape(v.to_s)}"
550
+ end.compact.join("&")
551
+ end
552
+
553
+ def filter_and_sort(instances=[], filters={})
554
+ order_by = filters.delete(:order)
555
+
556
+ filters.each do |filter, value|
557
+ value = value.to_s.upcase if (filter==:status || filter==:state)
558
+ if filter == :name || filter == :Name
559
+ instances = instances.select {|inst| inst.send(filter.to_s.capitalize) =~ /#{value}/}
560
+ else
561
+ instances = instances.select {|inst| inst.send(filter.to_s.capitalize) == value.to_s}
562
+ end
563
+ end
564
+
565
+ instances = instances.sort_by{|ins| ins.has_key?(order_by) ? ins.send(order_by) : 0 }
566
+ if order_by == "LaunchTime"
567
+ instances = instances.reverse
568
+ end
569
+
570
+ instances
571
+ end
572
+
573
+ def arrayize(array_or_object)
574
+ return [] unless array_or_object
575
+ array_or_object.is_a?(Array) ? array_or_object : [array_or_object]
576
+ end
577
+
578
+ # TODO: all methods below here can be nuked once CLI tools are removed
579
+ def run_ibm_tool(command)
580
+ cmd = "cd #{IBM_TOOLS_HOME} && ./#{command} -u #{@username} -w #{passphrase} -g #{password_file}"
581
+ logger.debug cmd
582
+ output = if @debug
583
+ IBMMockResponseGenerator.respond(command)
584
+ else
585
+ `#{cmd}`
586
+ end
587
+
588
+ logger.debug output
589
+
590
+ if output =~ /ErrorMessage : (.*)/
591
+ raise $1 # raise the error message matched above
592
+ else
593
+ return output
594
+ end
595
+ end
596
+
597
+ def create_password_file
598
+ # if no passphrase or password file is supplied, we'll generate it
599
+ run_ibm_tool("ic-create-password.sh -u #{@username} -p #{@password}")
600
+ end
601
+
602
+ def password_file
603
+ @password_file ||= "/tmp/smrtcloud-pwfile-#{Time.now.to_i.to_s}"
604
+ end
605
+
606
+ def passphrase
607
+ @passphrase ||= Time.now.to_i.to_s
608
+ end
609
+ # End CLI tool helpers
610
+
611
+
612
+
613
+ end
614
+
615
+ # predefine an instance for convenience
616
+ @smartcloud = IBMSmartCloud.new(ENV['SMARTCLOUD_USERNAME'], ENV['SMARTCLOUD_PASSWORD']) if ENV['SMARTCLOUD_USERNAME'] && ENV['SMARTCLOUD_PASSWORD']
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ #####################################################################################
3
+ # Copyright (c) 2011, Cohesive Flexible Technologies, Inc.
4
+ # This copyrighted material is the property of Cohesive Flexible Technologies and
5
+ # is subject to the license terms of the product it is contained within, whether
6
+ # in text or compiled form. It is licensed under the terms expressed in the
7
+ # accompanying README and LICENSE files.
8
+ #
9
+ # This program is AS IS and WITHOUT ANY WARRANTY; without even the implied warranty
10
+ # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+ #####################################################################################
12
+
13
+ class SmartcloudLogger < Logger
14
+ def format_message(severity, timestamp, foobar, message)
15
+ message = message.to_s
16
+
17
+ "#{timestamp.strftime("%b %d %H:%M:%S")} #{severity}: #{message}\n"
18
+ end
19
+
20
+ end