deltacloud-core 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (242) hide show
  1. data/LICENSE +145 -0
  2. data/NOTICE +10 -1
  3. data/Rakefile +50 -2
  4. data/bin/deltacloudd +111 -14
  5. data/config/addresses.xml +14 -0
  6. data/config/condor.yaml +30 -0
  7. data/config/drivers/azure.yaml +3 -0
  8. data/config/drivers/condor.yaml +3 -0
  9. data/config/{drivers.yaml → drivers/ec2.yaml} +5 -34
  10. data/config/drivers/eucalyptus.yaml +8 -0
  11. data/config/drivers/gogrid.yaml +3 -0
  12. data/config/drivers/mock.yaml +3 -0
  13. data/config/drivers/opennebula.yaml +4 -0
  14. data/config/drivers/rackspace.yaml +3 -0
  15. data/config/drivers/rhevm.yaml +3 -0
  16. data/config/drivers/rimuhosting.yaml +3 -0
  17. data/config/drivers/sbc.yaml +2 -0
  18. data/config/drivers/terremark.yaml +3 -0
  19. data/config/drivers/vsphere.yaml +8 -0
  20. data/deltacloud-core.gemspec +13 -5
  21. data/deltacloud.rb +4 -2
  22. data/lib/deltacloud/backend_capability.rb +2 -2
  23. data/lib/deltacloud/base_driver/base_driver.rb +23 -52
  24. data/lib/deltacloud/base_driver/exceptions.rb +168 -0
  25. data/lib/deltacloud/base_driver/features.rb +31 -12
  26. data/lib/deltacloud/base_driver/mock_driver.rb +2 -1
  27. data/lib/deltacloud/core_ext/string.rb +2 -0
  28. data/lib/deltacloud/drivers/azure/azure_driver.rb +5 -5
  29. data/lib/deltacloud/drivers/condor/condor_client.rb +273 -0
  30. data/lib/deltacloud/drivers/condor/condor_driver.rb +236 -0
  31. data/lib/deltacloud/drivers/condor/ip_agents/confserver.rb +75 -0
  32. data/lib/deltacloud/drivers/condor/ip_agents/default.rb +84 -0
  33. data/lib/deltacloud/drivers/ec2/ec2_driver.rb +326 -95
  34. data/lib/deltacloud/drivers/ec2/ec2_mock_driver.rb +3 -3
  35. data/lib/deltacloud/drivers/eucalyptus/eucalyptus_driver.rb +40 -8
  36. data/lib/deltacloud/drivers/gogrid/gogrid_client.rb +7 -7
  37. data/lib/deltacloud/drivers/gogrid/gogrid_driver.rb +42 -25
  38. data/lib/deltacloud/drivers/mock/data/{buckets/blobs → blobs}/blob1.yml +6 -4
  39. data/lib/deltacloud/drivers/mock/data/{buckets/blobs → blobs}/blob2.yml +7 -5
  40. data/lib/deltacloud/drivers/mock/data/{buckets/blobs → blobs}/blob3.yml +6 -4
  41. data/lib/deltacloud/drivers/mock/data/{buckets/blobs → blobs}/blob4.yml +6 -4
  42. data/lib/deltacloud/drivers/mock/data/{buckets/blobs → blobs}/blob5.yml +6 -4
  43. data/lib/deltacloud/drivers/mock/data/buckets/bucket1.yml +7 -1
  44. data/lib/deltacloud/drivers/mock/data/buckets/bucket2.yml +6 -1
  45. data/lib/deltacloud/drivers/mock/data/images/img1.yml +6 -2
  46. data/lib/deltacloud/drivers/mock/data/images/img2.yml +6 -2
  47. data/lib/deltacloud/drivers/mock/data/images/img3.yml +6 -2
  48. data/lib/deltacloud/drivers/mock/data/instances/inst0.yml +11 -10
  49. data/lib/deltacloud/drivers/mock/data/instances/inst1.yml +14 -7
  50. data/lib/deltacloud/drivers/mock/data/instances/inst2.yml +14 -7
  51. data/lib/deltacloud/drivers/mock/data/storage_snapshots/snap1.yml +3 -2
  52. data/lib/deltacloud/drivers/mock/data/storage_snapshots/snap2.yml +3 -2
  53. data/lib/deltacloud/drivers/mock/data/storage_snapshots/snap3.yml +3 -2
  54. data/lib/deltacloud/drivers/mock/data/storage_volumes/vol1.yml +4 -3
  55. data/lib/deltacloud/drivers/mock/data/storage_volumes/vol2.yml +4 -3
  56. data/lib/deltacloud/drivers/mock/data/storage_volumes/vol3.yml +4 -3
  57. data/lib/deltacloud/drivers/mock/mock_client.rb +101 -0
  58. data/lib/deltacloud/drivers/mock/mock_driver.rb +367 -429
  59. data/lib/deltacloud/drivers/opennebula/opennebula_driver.rb +6 -0
  60. data/lib/deltacloud/drivers/rackspace/rackspace_driver.rb +59 -9
  61. data/lib/deltacloud/drivers/rhevm/rhevm_client.rb +62 -8
  62. data/lib/deltacloud/drivers/rhevm/rhevm_driver.rb +100 -45
  63. data/lib/deltacloud/drivers/rimuhosting/rimuhosting_client.rb +3 -2
  64. data/lib/deltacloud/drivers/rimuhosting/rimuhosting_driver.rb +8 -11
  65. data/lib/deltacloud/drivers/sbc/sbc_client.rb +6 -6
  66. data/lib/deltacloud/drivers/sbc/sbc_driver.rb +16 -0
  67. data/lib/deltacloud/drivers/terremark/terremark_driver.rb +17 -12
  68. data/lib/deltacloud/drivers/vsphere/vsphere_client.rb +140 -0
  69. data/lib/deltacloud/drivers/vsphere/vsphere_driver.rb +405 -0
  70. data/lib/deltacloud/drivers/vsphere/vsphere_filemanager.rb +182 -0
  71. data/lib/deltacloud/hardware_profile.rb +1 -1
  72. data/lib/deltacloud/helpers.rb +2 -1
  73. data/lib/deltacloud/helpers/application_helper.rb +92 -20
  74. data/lib/deltacloud/helpers/blob_stream.rb +160 -12
  75. data/lib/deltacloud/helpers/conversion_helper.rb +6 -2
  76. data/lib/deltacloud/helpers/json_helper.rb +31 -0
  77. data/lib/deltacloud/models/address.rb +28 -0
  78. data/lib/deltacloud/models/base_model.rb +5 -1
  79. data/lib/deltacloud/models/blob.rb +1 -1
  80. data/lib/deltacloud/models/bucket.rb +10 -0
  81. data/lib/deltacloud/models/firewall.rb +22 -0
  82. data/lib/deltacloud/models/firewall_rule.rb +23 -0
  83. data/lib/deltacloud/models/image.rb +12 -0
  84. data/lib/deltacloud/models/instance.rb +20 -2
  85. data/lib/deltacloud/models/key.rb +1 -1
  86. data/lib/deltacloud/runner.rb +3 -3
  87. data/lib/deltacloud/validation.rb +3 -7
  88. data/lib/drivers.rb +7 -1
  89. data/lib/sinatra/body_proxy.rb +34 -0
  90. data/lib/sinatra/lazy_auth.rb +5 -0
  91. data/lib/sinatra/rabbit.rb +54 -31
  92. data/lib/sinatra/rack_accept.rb +157 -0
  93. data/lib/sinatra/rack_date.rb +38 -0
  94. data/lib/sinatra/rack_etag.rb +2 -3
  95. data/lib/sinatra/rack_matrix_params.rb +51 -29
  96. data/lib/sinatra/rack_runtime.rb +1 -1
  97. data/lib/sinatra/rack_syslog.rb +86 -0
  98. data/lib/sinatra/url_for.rb +14 -1
  99. data/public/images/address.png +0 -0
  100. data/public/images/balancer.png +0 -0
  101. data/public/images/blob.png +0 -0
  102. data/public/images/bucket.png +0 -0
  103. data/public/images/cloud.png +0 -0
  104. data/public/images/firewall.png +0 -0
  105. data/public/images/image.png +0 -0
  106. data/public/images/key.png +0 -0
  107. data/public/images/machine.png +0 -0
  108. data/public/images/profile.png +0 -0
  109. data/public/images/realm.png +0 -0
  110. data/public/images/snapshot.png +0 -0
  111. data/public/images/volume.png +0 -0
  112. data/public/javascripts/application.js +119 -16
  113. data/public/javascripts/jquery.min.js +18 -0
  114. data/public/javascripts/jquery.mobile-1.0b1.min.js +146 -0
  115. data/public/stylesheets/compiled/application.css +8 -0
  116. data/public/stylesheets/images/ajax-loader.png +0 -0
  117. data/public/{images → stylesheets/images}/bread-bg.png +0 -0
  118. data/public/{images → stylesheets/images}/error.png +0 -0
  119. data/public/{images → stylesheets/images}/grid.png +0 -0
  120. data/public/stylesheets/images/icon-search-black.png +0 -0
  121. data/public/stylesheets/images/icons-18-black.png +0 -0
  122. data/public/stylesheets/images/icons-18-white.png +0 -0
  123. data/public/stylesheets/images/icons-36-black.png +0 -0
  124. data/public/stylesheets/images/icons-36-white.png +0 -0
  125. data/public/{images → stylesheets/images}/logo-wide.png +0 -0
  126. data/public/{images → stylesheets/images}/pending.png +0 -0
  127. data/public/{images → stylesheets/images}/rails.png +0 -0
  128. data/public/{images → stylesheets/images}/running.png +0 -0
  129. data/public/{images → stylesheets/images}/stopped.png +0 -0
  130. data/public/{images → stylesheets/images}/topbar-bg.png +0 -0
  131. data/public/stylesheets/jquery.mobile-1.0b1.min.css +8 -0
  132. data/public/stylesheets/new.css +53 -0
  133. data/server.rb +487 -175
  134. data/support/condor/bash/cached_images.sh +8 -0
  135. data/support/condor/bash/cloud_exit_hook.sh +17 -0
  136. data/support/condor/bash/cloud_functions +175 -0
  137. data/support/condor/bash/cloud_prepare_hook.sh +20 -0
  138. data/support/condor/bash/libvirt_cloud_script.sh +13 -0
  139. data/support/condor/config/50condor_cloud.config +37 -0
  140. data/support/condor/config/50condor_cloud_node.config +37 -0
  141. data/support/condor/config/condor-cloud +2 -0
  142. data/support/condor/config/condor_config.local +44 -0
  143. data/support/fedora/deltacloud-core +48 -26
  144. data/support/fedora/deltacloud-core-config +26 -0
  145. data/support/fedora/deltacloud-core.spec +314 -68
  146. data/support/fedora/deltacloudd-fedora +5 -0
  147. data/tests/common.rb +34 -4
  148. data/tests/drivers/mock/api_test.rb +3 -3
  149. data/tests/drivers/mock/images_test.rb +12 -0
  150. data/tests/drivers/mock/instances_test.rb +2 -0
  151. data/tests/rabbit_test.rb +2 -2
  152. data/views/addresses/_address.html.haml +6 -0
  153. data/views/addresses/associate.html.haml +12 -0
  154. data/views/addresses/index.html.haml +9 -0
  155. data/views/addresses/index.xml.haml +4 -0
  156. data/views/addresses/show.html.haml +21 -0
  157. data/views/addresses/show.xml.haml +14 -0
  158. data/views/api/show.html.haml +6 -11
  159. data/views/api/show.xml.haml +2 -0
  160. data/views/blobs/new.html.haml +24 -23
  161. data/views/blobs/show.html.haml +30 -31
  162. data/views/buckets/index.html.haml +9 -21
  163. data/views/buckets/index.xml.haml +3 -7
  164. data/views/buckets/new.html.haml +13 -12
  165. data/views/buckets/show.html.haml +22 -22
  166. data/views/buckets/show.xml.haml +5 -3
  167. data/views/docs/collection.html.haml +23 -34
  168. data/views/docs/collection.xml.haml +2 -2
  169. data/views/docs/index.html.haml +9 -13
  170. data/views/docs/index.xml.haml +1 -1
  171. data/views/docs/operation.html.haml +28 -38
  172. data/views/docs/operation.xml.haml +1 -1
  173. data/views/drivers/index.html.haml +8 -13
  174. data/views/drivers/show.html.haml +18 -18
  175. data/views/error.html.haml +32 -27
  176. data/views/errors/400.html.haml +41 -0
  177. data/views/errors/{validation_failure.xml.haml → 400.xml.haml} +0 -4
  178. data/views/errors/401.html.haml +41 -0
  179. data/views/errors/{auth_exception.xml.haml → 401.xml.haml} +0 -0
  180. data/views/errors/403.html.haml +42 -0
  181. data/views/errors/{not_allowed.xml.haml → 403.xml.haml} +0 -0
  182. data/views/errors/404.html.haml +29 -0
  183. data/views/errors/{not_found.xml.haml → 404.xml.haml} +1 -1
  184. data/views/errors/405.html.haml +29 -0
  185. data/views/errors/405.xml.haml +5 -0
  186. data/views/errors/500.html.haml +43 -0
  187. data/views/errors/500.xml.haml +5 -0
  188. data/views/errors/502.html.haml +43 -0
  189. data/views/errors/{backend_error.xml.haml → 502.xml.haml} +1 -2
  190. data/views/errors/backend_capability_failure.html.haml +27 -9
  191. data/views/firewalls/index.html.haml +15 -0
  192. data/views/firewalls/index.xml.haml +28 -0
  193. data/views/firewalls/new.html.haml +11 -0
  194. data/views/firewalls/new_rule.html.haml +20 -0
  195. data/views/firewalls/show.html.haml +42 -0
  196. data/views/firewalls/show.xml.haml +26 -0
  197. data/views/hardware_profiles/index.html.haml +15 -23
  198. data/views/hardware_profiles/show.html.haml +22 -18
  199. data/views/images/index.html.haml +11 -23
  200. data/views/images/index.xml.haml +4 -13
  201. data/views/images/new.html.haml +12 -13
  202. data/views/images/show.html.haml +26 -20
  203. data/views/images/show.xml.haml +2 -1
  204. data/views/instance_states/show.html.haml +21 -25
  205. data/views/instances/index.html.haml +13 -30
  206. data/views/instances/index.xml.haml +2 -23
  207. data/views/instances/new.html.haml +83 -88
  208. data/views/instances/show.html.haml +53 -55
  209. data/views/instances/show.xml.haml +12 -10
  210. data/views/keys/index.html.haml +13 -24
  211. data/views/keys/new.html.haml +7 -7
  212. data/views/keys/show.html.haml +26 -21
  213. data/views/layout.html.haml +28 -27
  214. data/views/load_balancers/index.html.haml +11 -31
  215. data/views/load_balancers/index.xml.haml +0 -1
  216. data/views/load_balancers/new.html.haml +1 -1
  217. data/views/load_balancers/show.html.haml +33 -34
  218. data/views/load_balancers/show.xml.haml +2 -2
  219. data/views/realms/index.html.haml +11 -24
  220. data/views/realms/index.xml.haml +2 -8
  221. data/views/realms/show.html.haml +17 -15
  222. data/views/realms/show.xml.haml +2 -1
  223. data/views/storage_snapshots/index.html.haml +11 -21
  224. data/views/storage_snapshots/index.xml.haml +2 -5
  225. data/views/storage_snapshots/new.html.haml +1 -1
  226. data/views/storage_snapshots/show.html.haml +21 -13
  227. data/views/storage_snapshots/show.xml.haml +2 -1
  228. data/views/storage_volumes/index.html.haml +11 -34
  229. data/views/storage_volumes/new.html.haml +1 -1
  230. data/views/storage_volumes/show.html.haml +33 -27
  231. data/views/storage_volumes/show.xml.haml +2 -1
  232. metadata +266 -178
  233. data/lib/sinatra/respond_to.rb +0 -248
  234. data/support/fedora/deltacloudd +0 -128
  235. data/support/fedora/rubygem-deltacloud-core.spec +0 -127
  236. data/views/accounts/index.html.haml +0 -11
  237. data/views/accounts/show.html.haml +0 -30
  238. data/views/errors/auth_exception.html.haml +0 -8
  239. data/views/errors/backend_error.html.haml +0 -22
  240. data/views/errors/not_allowed.html.haml +0 -6
  241. data/views/errors/not_found.html.haml +0 -6
  242. data/views/errors/validation_failure.html.haml +0 -11
@@ -40,14 +40,15 @@ class RimuHostingClient
40
40
  if(!@auth.nil?)
41
41
  headers["Authorization"] = @auth
42
42
  end
43
+ safely do
43
44
  r = @service.send_request(method, @uri.path + resource, data, headers)
44
45
  puts r.body
45
46
  res = JSON.parse(r.body)
46
47
  res = res[res.keys[0]]
47
48
 
48
49
  if(res['response_type'] == "ERROR" and ( (res['error_info']['error_class'] == "PermissionException") or
49
- (res['error_info']['error_class'] == "LoginRequired") ))
50
- raise Deltacloud::AuthException.new
50
+ (res['error_info']['error_class'] == "LoginRequired") ))
51
+ raise "AuthFailure"
51
52
  end
52
53
  res
53
54
  end
@@ -108,13 +108,13 @@ class RimuHostingDriver < Deltacloud::BaseDriver
108
108
  end
109
109
 
110
110
  def create_instance(credentials, image_id, opts)
111
- rh = RimuHostingClient.new(credentials)
111
+ rh = RimuHostingClient.new(credentials)
112
112
  # really need to raise an exception here.
113
113
  hwp_id = opts[:hwp_id] || 1
114
- # really bad, but at least its a fqdn
115
- name = Time.now.to_s + '.com'
116
- if (opts[:name]) then
117
- name = opts[:name]
114
+ name = opts[:name]
115
+ if not name
116
+ # really bad, but at least its a fqdn
117
+ name = Time.now.to_i.to_s + '.com'
118
118
  end
119
119
  convert_srv_to_instance(rh.create_server(image_id, hwp_id, name))
120
120
 
@@ -149,15 +149,12 @@ class RimuHostingDriver < Deltacloud::BaseDriver
149
149
  stopped.to( :finish ) .automatically
150
150
  end
151
151
 
152
- def safely(&block)
153
- begin
154
- block.call
155
- rescue Exception => e
156
- raise Deltacloud::BackendError.new(500, e.class.to_s, e.message, e.backtrace)
152
+ exceptions do
153
+ on /AuthFailure/ do
154
+ status 401
157
155
  end
158
156
  end
159
157
 
160
-
161
158
  end
162
159
 
163
160
  end
@@ -22,9 +22,9 @@ require 'digest/md5'
22
22
  module Deltacloud
23
23
  module Drivers
24
24
  module SBC
25
-
25
+
26
26
  class FixtureNotFound < Exception; end
27
-
27
+
28
28
  #
29
29
  # Client for the IBM Smart Business Cloud (SBC).
30
30
  #
@@ -180,9 +180,9 @@ class SBCClient
180
180
  #
181
181
  def backend_error!(resp)
182
182
  if resp.is_a?(Net::HTTPUnauthorized)
183
- raise Deltacloud::AuthException, resp.message
183
+ raise "AuthFailure"
184
184
  else
185
- raise Deltacloud::BackendError.new(resp.code, resp.body, resp.message, '')
185
+ raise "BackendError"
186
186
  end
187
187
  end
188
188
 
@@ -192,7 +192,7 @@ class SBCClient
192
192
  def urlencode(hash)
193
193
  hash.keys.map { |k| "#{URI.encode(k)}=#{URI.encode(hash[k])}" }.join("&")
194
194
  end
195
-
195
+
196
196
  #
197
197
  # Reads a fake URL from local fixtures
198
198
  #
@@ -240,7 +240,7 @@ class SBCClient
240
240
  retry
241
241
  end
242
242
  end
243
-
243
+
244
244
  end
245
245
  end
246
246
  end
@@ -88,6 +88,10 @@ class SBCDriver < Deltacloud::BaseDriver
88
88
  body['location'] = opts[:realm_id] || @last_image['location']
89
89
  body['instanceType'] = opts[:hwp_id].gsub('-', '/') || @last_image['supportedInstanceTypes'][0]['id']
90
90
 
91
+ if not body['name']
92
+ body['name'] = Time.now.to_i.to_s
93
+ end
94
+
91
95
  # Submit instance, parse response
92
96
  convert_instance(sbc_client.create_instance(body).map[0])
93
97
  end
@@ -118,6 +122,18 @@ class SBCDriver < Deltacloud::BaseDriver
118
122
  instance(credentials, instance_id)
119
123
  end
120
124
 
125
+ exceptions do
126
+
127
+ on /AuthFailure/ do
128
+ status 401
129
+ end
130
+
131
+ on /BackendError/ do
132
+ status 502
133
+ end
134
+
135
+ end
136
+
121
137
  #
122
138
  # --------------------- Private helpers ---------------------
123
139
  #
@@ -31,7 +31,11 @@ module Deltacloud
31
31
 
32
32
  class TerremarkDriver < Deltacloud::BaseDriver
33
33
 
34
- feature :instances, :user_name
34
+ feature :instances, :user_name do
35
+ constraint :max_length, 15
36
+ end
37
+
38
+ USER_NAME_MAX = feature(:instances, :user_name).constraints[:max_length]
35
39
 
36
40
  #--
37
41
  # Vapp State Map... for use with convert_instance (get an integer back from terremark)
@@ -128,9 +132,13 @@ VAPP_STATE_MAP = { "0" => "PENDING", "1" => "PENDING", "2" => "STOPPED", "4"
128
132
  new_vapp = nil
129
133
  vapp_opts = {} #assemble options to pass to Fog::Terremark::Real.instantiate_vapp_template
130
134
  terremark_hwp = hardware_profiles(credentials, {:name => 'default'}).first #sanity check values against default
131
- name = opts['name'] #name could be nil or length 0 or too long
132
- name = "inst#{Time.now.to_i}" if (name.nil? || (name.length == 0))
133
- name = name.slice(0..13) #name < 15 chars (says terremark)
135
+ name = opts[:name]
136
+ if not name
137
+ name = "inst#{Time.now.to_i}"
138
+ end
139
+ if name.length > USER_NAME_MAX
140
+ raise "Parameter name must be #{USER_NAME_MAX} characters or less"
141
+ end
134
142
  unless ( (terremark_hwp.include?(:cpu, opts[:hwp_cpu].to_i)) &&
135
143
  (terremark_hwp.include?(:memory, opts[:hwp_memory].to_i)) ) then
136
144
  raise Deltacloud::Validation::Failure.new(Deltacloud::Validation::Param.new(["cpu"]), "Error with cpu and/or memory values. you said cpu->#{opts[:hwp_cpu]} and mem->#{opts[:hwp_memory]}")
@@ -192,7 +200,7 @@ end
192
200
  def valid_credentials?(credentials)
193
201
  begin
194
202
  new_client(credentials)
195
- rescue Deltacloud::AuthException
203
+ rescue
196
204
  return false
197
205
  end
198
206
  true
@@ -272,20 +280,17 @@ end
272
280
  vdc_id = terremark_client.default_vdc_id
273
281
  end
274
282
  if (vdc_id.nil?)
275
- raise DeltaCloud::AuthException.new
283
+ raise "AuthFailure"
276
284
  end
277
285
  terremark_client
278
286
  end
279
287
 
280
- def safely(&block)
281
- begin
282
- block.call
283
- rescue Exception => e
284
- raise Deltacloud::BackendError.new(500, e.class.to_s, e.message, e.backtrace)
288
+ exceptions do
289
+ on /AuthFailure/ do
290
+ status 401
285
291
  end
286
292
  end
287
293
 
288
-
289
294
  end
290
295
 
291
296
  end
@@ -0,0 +1,140 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more
2
+ # contributor license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright ownership. The
4
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
5
+ # "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations
14
+ # under the License.
15
+ #
16
+
17
+ module Deltacloud::Drivers::VSphere
18
+
19
+ require 'deltacloud/drivers/vsphere/vsphere_filemanager'
20
+
21
+ module Helper
22
+
23
+ # Find a VirtualMachine traversing through all Datastores and Datacenters
24
+ #
25
+ # This helper will return a Hash: { :datastore => NAME_OF_DS, :instance => VM }
26
+ # Returning datastore is necesarry for constructing a correct realm for an
27
+ # instance
28
+ #
29
+ def find_vm(credentials, name)
30
+ vsphere = new_client(credentials)
31
+ safely do
32
+ rootFolder = vsphere.serviceInstance.content.rootFolder
33
+ vm = {}
34
+ rootFolder.childEntity.grep(RbVmomi::VIM::Datacenter).each do |dc|
35
+ dc.datastoreFolder.childEntity.collect do |datastore|
36
+ vm[:instance] = datastore.vm.find { |x| x.name == name }
37
+ if vm[:instance]
38
+ vm[:datastore] = datastore.name
39
+ break
40
+ end
41
+ stored_tasks(datastore, vsphere) do |task|
42
+ if task.info.entity.class == RbVmomi::VIM::VirtualMachine and ['queued', 'running'].member? task.info.state
43
+ vm = { :stored_instance => load_serialized_instance(datastore,task.info.key), :datastore => datastore.name }
44
+ end
45
+ end
46
+ end
47
+ break if [:datastore]
48
+ end
49
+ vm
50
+ end
51
+ end
52
+
53
+ # Find a ResourcePool[1] object associated by given Datastore
54
+ # ResourcePool is defined for Datacenter and is used for launching a new
55
+ # instance
56
+ #
57
+ # [1] http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.ResourcePool.html
58
+ #
59
+ def find_resource_pool(credentials, name)
60
+ vsphere = new_client(credentials)
61
+ safely do
62
+ rootFolder = vsphere.serviceInstance.content.rootFolder
63
+ dc = rootFolder.childEntity.grep(RbVmomi::VIM::Datacenter).select do |dc|
64
+ dc.datastoreFolder.childEntity.find { |d| d.name == name }.nil? == false
65
+ end.flatten.compact.first
66
+ dc.hostFolder.childEntity.collect.first.resourcePool
67
+ end
68
+ end
69
+
70
+ # This helper will try to find a Datastore[1] object in all Datacenters.
71
+ # Datastore is used to place instance on create to correct place
72
+ #
73
+ # [1] http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.Datastore.html
74
+ #
75
+ def find_datastore(credentials, name)
76
+ vsphere = new_client(credentials)
77
+ safely do
78
+ rootFolder = vsphere.serviceInstance.content.rootFolder
79
+ rootFolder.childEntity.grep(RbVmomi::VIM::Datacenter).collect do |dc|
80
+ dc.datastoreFolder.childEntity.find { |d| d.name == name }
81
+ end.flatten.compact.first
82
+ end
83
+ end
84
+
85
+ # This helper will traverse across all datacenters and datastores and gather
86
+ # all virtual machines available on vSphere
87
+ #
88
+ def list_virtual_machines(credentials)
89
+ vsphere = new_client(credentials)
90
+ vms = []
91
+ rootFolder = vsphere.serviceInstance.content.rootFolder
92
+ rootFolder.childEntity.grep(RbVmomi::VIM::Datacenter).each do |dc|
93
+ dc.datastoreFolder.childEntity.each do |datastore|
94
+ vms += datastore.vm.collect { |vm| { :instance => vm, :datastore => datastore.name } unless vm.nil? }
95
+ stored_tasks(datastore, vsphere) do |task|
96
+ if task.info.entity.class == RbVmomi::VIM::VirtualMachine
97
+ vms << { :stored_instance => load_serialized_instance(datastore, task.info.key), :datastore => datastore.name }
98
+ end
99
+ end
100
+ end
101
+ end
102
+ vms.flatten.compact
103
+ end
104
+
105
+ # Map given instance to task. Task name is used as a filename.
106
+ #
107
+ def map_task_to_instance(datastore, task_key, new_instance)
108
+ VSphere::FileManager::store_mapping!(datastore, YAML::dump(new_instance).to_s, task_key)
109
+ new_instance
110
+ end
111
+
112
+ def load_serialized_instance(datastore, task_key)
113
+ VSphere::FileManager::load_mapping(datastore, task_key)
114
+ end
115
+
116
+ # Yield all tasks if they are included in mapper storage directory.
117
+ def stored_tasks(datastore, vsphere)
118
+ tasks = VSphere::FileManager::list_mappings(datastore)
119
+ return [] if tasks.empty?
120
+ vsphere.serviceInstance.content.taskManager.recentTask.each do |task|
121
+ if tasks.include?(task.info.key) and ['queued', 'running'].member?(task.info.state)
122
+ yield task
123
+ tasks.delete(task.info.key)
124
+ end
125
+ end
126
+ # Delete old left tasks
127
+ tasks.select { |f| f =~ /task-(\d+)/ }.each do |task|
128
+ VSphere::FileManager::delete_mapping!(datastore, task)
129
+ end
130
+ end
131
+
132
+ def extract_architecture(text)
133
+ 'x86_64' if text.include?('64-bit')
134
+ 'i386' if text.include?('32-bit')
135
+ end
136
+
137
+
138
+ end
139
+
140
+ end
@@ -0,0 +1,405 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more
2
+ # contributor license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright ownership. The
4
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
5
+ # "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations
14
+ # under the License.
15
+ #
16
+
17
+ require 'deltacloud/base_driver'
18
+ require 'rbvmomi'
19
+ require 'deltacloud/drivers/vsphere/vsphere_client'
20
+
21
+ module Deltacloud::Drivers::VSphere
22
+
23
+ MAPPER_STORAGE_ROOT = File::join("/var/tmp", "deltacloud-vsphere-#{ENV["USER"]}")
24
+
25
+ class VSphereDriver < Deltacloud::BaseDriver
26
+
27
+ include Deltacloud::Drivers::VSphere::Helper
28
+ include Deltacloud::Drivers::VSphere::FileManager
29
+
30
+ # You can use 'user_iso' feature to set 'user_iso' parameter when creating
31
+ # a new instance where this parameter can hold gzipped CDROM iso which will
32
+ # be mounted into created instance after boot
33
+ feature :instances, :user_iso
34
+ feature :instances, :user_data
35
+ feature :instances, :user_name
36
+
37
+ # There is just one hardware profile where memory is measured using maximum
38
+ # memory available on ESX for virtual machines and CPU using maximum free
39
+ # CPU cores in ESX.
40
+ def hardware_profiles(credentials, opts={})
41
+ vsphere = new_client(credentials)
42
+ safely do
43
+ service = vsphere.serviceInstance.content
44
+ max_memory, max_cpu_cores = 0, 0
45
+ service.rootFolder.childEntity.grep(RbVmomi::VIM::Datacenter).each do |dc|
46
+ max_memory += dc.hostFolder.childEntity.first.summary.effectiveMemory
47
+ max_cpu_cores += dc.hostFolder.childEntity.first.summary.numCpuCores
48
+ end
49
+ [Deltacloud::HardwareProfile::new('default') do
50
+ cpu (1..max_cpu_cores)
51
+ memory (128..max_memory)
52
+ architecture ['x86_64', 'i386']
53
+ end]
54
+ end
55
+ end
56
+
57
+ # Configure instance state machine
58
+ define_instance_states do
59
+ start.to(:pending) .on( :create )
60
+ pending.to(:stopped) .automatically
61
+ stopped.to(:running) .on( :start )
62
+ running.to(:running) .on( :reboot )
63
+ running.to(:shutting_down) .on( :stop )
64
+ shutting_down.to(:stopped) .automatically
65
+ stopped.to(:finish) .on( :destroy )
66
+ end
67
+
68
+
69
+ # Images are virtual machines with 'template' flag set to be true.
70
+ # Thus we're getting them using find_vm and list_virtual_machines
71
+ def images(credentials, opts=nil)
72
+ cloud = new_client(credentials)
73
+ img_arr = []
74
+
75
+ # Skip traversing through all instances in all datacenters when ID
76
+ # attribute is set
77
+ safely do
78
+ if opts[:id]
79
+ template_vms = [ find_vm(credentials, opts[:id]) ].select { |vm| vm[:instance] }.compact
80
+ else
81
+ template_vms = list_virtual_machines(credentials).select { |vm| vm[:instance] && vm[:instance].summary.config[:template] }
82
+ end
83
+ img_arr = template_vms.collect do |image_hash|
84
+ image, realm = image_hash[:instance], image_hash[:datastore]
85
+ config = image.summary.config
86
+ instance_state = convert_state(:instance, image.summary.runtime[:powerState])
87
+ # Preload all properties to save multiple SOAP calls to vSphere
88
+ properties = {
89
+ :name => config[:name],
90
+ :full_name => config[:guestFullName]
91
+ }
92
+ image_state = convert_state(:image, image.summary.runtime[:powerState])
93
+ # This will determine image architecture using image description.
94
+ # Ussualy description include '64-bit' or '32-bit'. In case you used
95
+ # some weird template/OS it will fallback to 64 bit
96
+ image_architecture = extract_architecture(properties[:full_name]) || 'x86_64'
97
+ Image.new(
98
+ :id => properties[:name],
99
+ :name => properties[:name],
100
+ :architecture => image_architecture,
101
+ :owner_id => credentials.user,
102
+ :description => properties[:full_name],
103
+ :state => image_state
104
+ )
105
+ end
106
+ end
107
+ img_arr = filter_on( img_arr, :architecture, opts )
108
+ img_arr.sort_by{|e| [e.owner_id, e.name]}
109
+ end
110
+
111
+ def create_image(credentials, opts={})
112
+ vsphere = new_client(credentials)
113
+ safely do
114
+ find_vm(credentials, opts[:id])[:instance].MarkAsTemplate
115
+ end
116
+ images(credentials, :id => opts[:id])
117
+ end
118
+
119
+ # List all datacenters managed by the vSphere or vCenter entrypoint.
120
+ def realms(credentials, opts=nil)
121
+ vsphere = new_client(credentials)
122
+ safely do
123
+ if opts and opts[:id]
124
+ datastore = find_datastore(credentials, opts[:id])
125
+ [convert_realm(datastore)]
126
+ else
127
+ rootFolder = vsphere.serviceInstance.content.rootFolder
128
+ rootFolder.childEntity.grep(RbVmomi::VIM::Datacenter).collect do |dc|
129
+ dc.datastoreFolder.childEntity.collect { |datastore| convert_realm(datastore) }
130
+ end.flatten
131
+ end
132
+ end
133
+ end
134
+
135
+ # List all running instances, across all datacenters. DeltaCloud API does
136
+ # not yet support filtering instances by realm.
137
+ def instances(credentials, opts=nil)
138
+ cloud = new_client(credentials)
139
+ inst_arr, machine_vms, pending_vms = [], [], []
140
+ safely do
141
+ # Using find_vm is a way faster than listing all virtual machines
142
+ if opts[:id]
143
+ list_vms = [ find_vm(credentials, opts[:id]) ].compact
144
+ else
145
+ list_vms = list_virtual_machines(credentials)
146
+ end
147
+ # Split machines to the 'real' one and PENDING one.
148
+ machine_vms = list_vms.select { |vm| vm[:instance] && !vm[:instance].summary.config[:template] }
149
+ pending_vms = list_vms.select { |vm| vm[:stored_instance] }.collect { |vm| vm[:stored_instance]}
150
+ end
151
+ safely do
152
+ inst_arr = machine_vms.collect do |vm_hash|
153
+ # Since all calls to vm are threaten as SOAP calls, reduce them using
154
+ # local variable.
155
+ vm, realm_id = vm_hash[:instance], vm_hash[:datastore]
156
+ config = vm.summary.config
157
+ next if not config
158
+ # Template (image_id) is beeing stored as 'extraConfig' parameter in
159
+ # instance.
160
+ template_id = vm.config[:extraConfig].select { |k| k.key == 'template_id' }
161
+ template_id = template_id.first.value unless template_id.empty?
162
+ properties = {
163
+ :memory => config[:memorySizeMB],
164
+ :cpus => config[:numCpu],
165
+ :storage => vm.summary.storage[:unshared],
166
+ :name => config[:name],
167
+ :full_name => config[:guestFullName],
168
+ }
169
+ instance_state = convert_state(:instance, vm.summary.runtime[:powerState])
170
+ instance_profile = InstanceProfile::new('default',
171
+ :hwp_cpu => properties[:cpus],
172
+ :hwp_memory => properties[:memory],
173
+ :hwp_storage => properties[:storage])
174
+
175
+ # We're getting IP address from 'vmware guest tools'.
176
+ # If guest tools are not installed, we return list of MAC addresses
177
+ # assigned to this instance.
178
+ instance_address = vm.guest[:net].empty? ? vm.macs.values.first : vm.guest[:net].first[:ipAddress].first
179
+ Instance.new(
180
+ :id => properties[:name],
181
+ :name => properties[:name],
182
+ :owner_id => credentials.user,
183
+ :image_id => template_id,
184
+ :description => properties[:full_name],
185
+ :realm_id => realm_id,
186
+ :state => instance_state,
187
+ :public_addresses => instance_address,
188
+ :private_addresses => [],
189
+ :instance_profile => instance_profile,
190
+ :actions => instance_actions_for( instance_state ),
191
+ :create_image => true
192
+ )
193
+ end
194
+ end
195
+ inst_arr.compact!
196
+ # Append 'temporary' instances to real instances.
197
+ # 'Temporary' or 'stored' instance are used to speed up instance creation
198
+ # process by serializing instances to datastore and map instance to task.
199
+ #
200
+ inst_arr += pending_vms
201
+ filter_on( inst_arr, :state, opts )
202
+ end
203
+
204
+
205
+ def create_instance(credentials, image_id, opts)
206
+ vsphere = new_client(credentials)
207
+ safely do
208
+ rootFolder = vsphere.serviceInstance.content.rootFolder
209
+ vm = find_vm(credentials, opts[:image_id])
210
+ # New instance need valid resource pool and datastore to be placed.
211
+ # For this reason, realm_id **needs** to be set.
212
+ if opts and opts[:realm_id]
213
+ resourcePool = find_resource_pool(credentials, opts[:realm_id])
214
+ datastore = find_datastore(credentials, opts[:realm_id])
215
+ else
216
+ resourcePool = find_resource_pool(credentials, vm[:datastore])
217
+ datastore = find_datastore(credentials, vm[:datastore])
218
+ end
219
+ relocate = { :pool => resourcePool, :datastore => datastore }
220
+ relocateSpec = RbVmomi::VIM.VirtualMachineRelocateSpec(relocate)
221
+ # Set extra configuration for VM, like template_id
222
+ machine_config = {
223
+ :memoryMB => opts[:hwp_memory],
224
+ :numCPUs => opts[:hwp_cpu],
225
+ :extraConfig => [
226
+ { :key => 'template_id', :value => image_id },
227
+ ]
228
+ }
229
+ if (opts[:user_data] and not opts[:user_data].empty?) and (opts[:user_iso] and not opts[:user_iso].empty?)
230
+ raise "ERROR: You cannot use user_data and user_iso features together"
231
+ end
232
+ # If user wants to inject data into instance he need to submit a Base64
233
+ # encoded gzipped ISO image.
234
+ # This image will be uplaoded to the Datastore given in 'realm_id'
235
+ # parameter and them attached to instance.
236
+ if opts[:user_data] and not opts[:user_data].empty?
237
+ device = vm[:instance].config.hardware.device.select { |hw| hw.class == RbVmomi::VIM::VirtualCdrom }.first
238
+ if device
239
+ VSphere::FileManager::user_data!(datastore, opts[:user_data], "#{opts[:name]}.iso")
240
+ machine_config[:extraConfig] << {
241
+ :key => 'user_data_file', :value => "#{opts[:name]}.iso"
242
+ }
243
+ device.backing = RbVmomi::VIM.VirtualCdromIsoBackingInfo(:fileName => "[#{opts[:realm_id] || vm[:datastore]}] "+
244
+ "/#{VSphere::FileManager::DIRECTORY_PATH}/#{opts[:name]}.iso")
245
+ machine_config.merge!({
246
+ :deviceChange => [{
247
+ :operation => :edit,
248
+ :device => device
249
+ }]
250
+ })
251
+ else
252
+ raise "Failed to inject data to device because there is no CD-ROM drive defined in given template"
253
+ end
254
+ end
255
+ if opts[:user_iso] and not opts[:user_iso].empty?
256
+ device = vm[:instance].config.hardware.device.select { |hw| hw.class == RbVmomi::VIM::VirtualCdrom }.first
257
+ if device
258
+ VSphere::FileManager::store_iso!(datastore, opts[:user_iso], "#{opts[:name]}.iso")
259
+ machine_config[:extraConfig] << {
260
+ :key => 'user_iso_file', :value => "#{opts[:name]}.iso"
261
+ }
262
+ device.backing = RbVmomi::VIM.VirtualCdromIsoBackingInfo(:fileName => "[#{opts[:realm_id] || vm[:datastore]}] "+
263
+ "/#{VSphere::FileManager::DIRECTORY_PATH}/#{opts[:name]}.iso")
264
+ machine_config.merge!({
265
+ :deviceChange => [{
266
+ :operation => :edit,
267
+ :device => device
268
+ }]
269
+ })
270
+ else
271
+ raise "Failed to inject data to device because there is no CD-ROM drive defined in given template"
272
+ end
273
+ end
274
+ spec = RbVmomi::VIM.VirtualMachineCloneSpec(
275
+ :location => relocateSpec,
276
+ :powerOn => true,
277
+ :template => false,
278
+ :config => RbVmomi::VIM.VirtualMachineConfigSpec(machine_config)
279
+ )
280
+ instance_profile = InstanceProfile::new('default', :hwp_memory => opts[:hwp_memory], :hwp_cpu => opts[:hwp_cpu])
281
+ task = vm[:instance].CloneVM_Task(:folder => vm[:instance].parent, :name => opts[:name], :spec => spec)
282
+ new_instance = Instance::new(
283
+ :id => opts[:name],
284
+ :name => opts[:name],
285
+ :description => opts[:name],
286
+ :owner_id => credentials.user,
287
+ :image_id => opts[:image_id],
288
+ :realm_id => opts[:realm_id] || vm[:datastore],
289
+ :state => 'PENDING',
290
+ :public_addresses => [],
291
+ :private_addresses => [],
292
+ :instance_profile => instance_profile,
293
+ :actions => instance_actions_for( 'PENDING' ),
294
+ :create_image => false
295
+ )
296
+ # This will 'serialize' instance to YAML file and map it to the task.
297
+ # Ussualy it takes like 2-3 minutes (depending on storage size) to
298
+ # complete instance cloning process.
299
+ map_task_to_instance(datastore, task.info.key, new_instance)
300
+ end
301
+ end
302
+
303
+ # Reboot an instance, given its id.
304
+ def reboot_instance(credentials, id)
305
+ find_vm(credentials, id)[:instance].ResetVM_Task
306
+ end
307
+
308
+ # Start an instance, given its id.
309
+ def start_instance(credentials, id)
310
+ find_vm(credentials, id)[:instance].PowerOnVM_Task
311
+ end
312
+
313
+ # Stop an instance, given its id.
314
+ def stop_instance(credentials, id)
315
+ find_vm(credentials, id)[:instance].PowerOffVM_Task
316
+ end
317
+
318
+ # Destroy an instance, given its id. Note that this will destroy all
319
+ # instance data.
320
+ #
321
+ # If there is user-data dile asocciated with instance, remove this file as
322
+ # well.
323
+ def destroy_instance(credentials, instance_id)
324
+ vm = find_vm(credentials, instance_id)
325
+ user_file = vm[:instance].config[:extraConfig].select { |k| k.key == 'user_iso_file' }.first
326
+ VSphere::FileManager::delete_iso!(vm[:instance].send(:datastore).first, user_file.value) if user_file
327
+ vm[:instance].Destroy_Task.wait_for_completion
328
+ end
329
+
330
+ alias :destroy_image :destroy_instance
331
+
332
+ exceptions do
333
+
334
+ on /InvalidLogin/ do
335
+ status 401
336
+ end
337
+
338
+ on /ERROR/ do
339
+ status 500
340
+ end
341
+
342
+ on /RbVmomi::Fault/ do
343
+ status 502
344
+ end
345
+
346
+ on /Failed to inject data/ do
347
+ status 502
348
+ end
349
+
350
+ end
351
+
352
+ def valid_credentials?(credentials)
353
+ begin
354
+ RbVmomi::VIM.connect(:host => host_endpoint, :user => credentials.user, :password => credentials.password, :insecure => true)
355
+ return true
356
+ rescue
357
+ return false
358
+ end
359
+ end
360
+
361
+ #######
362
+ private
363
+ #######
364
+
365
+ def new_client(credentials)
366
+ safely do
367
+ RbVmomi::VIM.connect(:host => host_endpoint, :user => credentials.user, :password => credentials.password, :insecure => true)
368
+ end
369
+ end
370
+
371
+ def host_endpoint
372
+ endpoint = api_provider
373
+ endpoint || Deltacloud::Drivers::driver_config[:vsphere][:entrypoints]['default']['default']
374
+ end
375
+
376
+ def convert_realm(datastore)
377
+ Realm::new(
378
+ :id => datastore.name,
379
+ :name => datastore.name,
380
+ :limit => datastore.summary.freeSpace,
381
+ :state => datastore.summary.accessible ? 'AVAILABLE' : 'UNAVAILABLE'
382
+ )
383
+ end
384
+
385
+ def convert_state(object, state)
386
+ new_state = ''
387
+ if object == :image
388
+ new_state = case state
389
+ when 'poweredOff' then 'AVAILABLE'
390
+ when 'poweredOn' then 'UNAVAILABLE'
391
+ end
392
+ end
393
+ if object == :instance
394
+ new_state = case state
395
+ when 'poweredOff' then 'STOPPED'
396
+ when 'poweredOn' then 'RUNNING'
397
+ else 'PENDING'
398
+ end
399
+ end
400
+ new_state
401
+ end
402
+
403
+ end
404
+
405
+ end