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
@@ -0,0 +1,182 @@
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 'nokogiri'
18
+
19
+ module VSphere
20
+ module FileManager
21
+
22
+
23
+ DIRECTORY_PATH="deltacloud"
24
+ MKISOFS_EXECUTABLE="mkisofs"
25
+ # This value is setted in this way because
26
+ # mkisofs man said, less than this amount
27
+ # he have to pad the content of the iso file
28
+ # that mean a limit of 400 kb file since
29
+ # 1 sector of iso file = 2048 bytes
30
+ ISO_SECTORS=200
31
+
32
+ RbVmomi::VIM::Datastore::class_eval do
33
+ def soap
34
+ @soap
35
+ end
36
+ end
37
+
38
+ class << self
39
+
40
+ def store_iso!(datastore,base64_iso, file_name)
41
+ file = StringIO.new(get_plain_iso(base64_iso).read)
42
+ uploadFile(datastore, file, file_name)
43
+ end
44
+
45
+ def user_data!(datastore,base64_content,file_name)
46
+ command="#{MKISOFS_EXECUTABLE} -stream-file-name #{file_name}.txt -stream-media-size #{ISO_SECTORS}"
47
+ iso_file=''
48
+ Open3::popen3(command) do |stdin, stdout, stderr|
49
+ stdin.write(base64_content.unpack("m"))
50
+ stdin.close()
51
+ iso_file=StringIO::new(stdout.read)
52
+ end
53
+ uploadFile(datastore,iso_file,file_name)
54
+ end
55
+
56
+
57
+ def delete_iso!(datastore,file_name)
58
+ deleteFile(datastore, file_name)
59
+ end
60
+
61
+ def store_mapping!(datastore, yaml_object, file_name)
62
+ file = StringIO::new(yaml_object)
63
+ uploadFile(datastore, file, file_name)
64
+ end
65
+
66
+ def delete_mapping!(datastore, file_name)
67
+ deleteFile(datastore, file_name)
68
+ end
69
+
70
+ def load_mapping(datastore, file_name)
71
+ YAML::load(downloadFile(datastore, file_name))
72
+ end
73
+
74
+ def list_mappings(datastore)
75
+ listFolder(datastore)
76
+ end
77
+
78
+ private
79
+
80
+ def make_directory!(datastore,directory)
81
+ dc=datastore.send(:datacenter)
82
+ dc._connection.serviceContent.fileManager.MakeDirectory :name => "[#{datastore.name}] #{directory}",
83
+ :datacenter => dc,
84
+ :createParentDirectories => false
85
+ end
86
+
87
+ def _exist?(datastore,file=nil)
88
+ uri = buildUrl(datastore,file) if file
89
+ uri = buildUrl(datastore) unless file
90
+ http = Net::HTTP.new(uri.host,uri.port)
91
+ http.use_ssl = true
92
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
93
+ headers = {
94
+ 'cookie' => datastore.send(:soap).cookie,
95
+ }
96
+ request = Net::HTTP::Head.new(uri.request_uri,headers)
97
+ res = http.request(request)
98
+ if res.kind_of?(Net::HTTPSuccess)
99
+ return true
100
+ else
101
+ return false
102
+ end
103
+ end
104
+
105
+ def downloadFile(datastore,file_name)
106
+ @uri = buildUrl(datastore,file_name)
107
+ http=Net::HTTP.new(@uri.host, @uri.port)
108
+ http.use_ssl = true
109
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
110
+ headers = { 'cookie' => datastore.send(:soap).cookie }
111
+ request = Net::HTTP::Get.new(@uri.request_uri, headers)
112
+ res = http.request(request)
113
+ raise "download failed: #{res.message}" unless res.kind_of?(Net::HTTPSuccess)
114
+ res.body
115
+ end
116
+
117
+ def deleteFile(datastore, file)
118
+ @uri = buildUrl(datastore, file)
119
+ http=Net::HTTP.new(@uri.host, @uri.port)
120
+ http.use_ssl = true
121
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
122
+ headers = { 'cookie' => datastore.send(:soap).cookie }
123
+ request = Net::HTTP::Delete.new(@uri.request_uri, headers)
124
+ res = http.request(request)
125
+ unless res.kind_of?(Net::HTTPSuccess) or res.kind_of?(Net::HTTPServiceUnavailable) or res.kind_of?(Net::HTTPNotFound)
126
+ raise "delete failed: #{res.message} #{file}"
127
+ end
128
+ end
129
+
130
+ def listFolder(datastore, folder="")
131
+ @uri = buildUrl(datastore, folder)
132
+ http=Net::HTTP.new(@uri.host, @uri.port)
133
+ http.use_ssl = true
134
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
135
+ headers = { 'cookie' => datastore.send(:soap).cookie }
136
+ request = Net::HTTP::Get.new(@uri.request_uri, headers)
137
+ begin
138
+ res = http.request(request)
139
+ Nokogiri::HTML(res.body).css("table tr a").map { |f| f.text.strip }.reject { |f| f == 'Parent Directory'}
140
+ rescue
141
+ puts "[ERROR]: Unable to list deltacloud folder"
142
+ []
143
+ end
144
+ end
145
+
146
+ def uploadFile(datastore,file,file_name)
147
+ raise "You need to set the realm_id" if datastore.nil?
148
+ make_directory!(datastore,DIRECTORY_PATH) unless _exist?(datastore)
149
+ @uri = buildUrl(datastore,file_name)
150
+ http=Net::HTTP.new(@uri.host, @uri.port)
151
+ http.use_ssl = true
152
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
153
+ headers = {
154
+ 'cookie' => datastore.send(:soap).cookie,
155
+ 'content-length' => file.size.to_s,
156
+ 'Content-Type' => 'application/octet-stream',
157
+ }
158
+ request = Net::HTTP::Put.new(@uri.request_uri, headers)
159
+ request.body_stream = file
160
+ res = http.request(request)
161
+ raise "upload failed: #{res.message}" unless res.kind_of?(Net::HTTPSuccess)
162
+ end
163
+
164
+ # return the url like https://<server_address>/folder/<path>/<file_name>?<query_infos>
165
+
166
+ def buildUrl(datastore,file="")
167
+ uri=URI::HTTPS::build(:host=>datastore._connection.http.address)
168
+ uri.path= ["/folder",DIRECTORY_PATH,file].join("/") if file
169
+ query={:dcPath => datastore.send(:datacenter).name, :dsName => datastore.name }
170
+ uri.query=query.collect{|name, value| "#{name}=#{URI.escape value}"}.join("&")
171
+ uri
172
+ end
173
+
174
+ def get_plain_iso(stream)
175
+ unbase64file=stream.unpack('m').to_s
176
+ Zlib::GzipReader.new(StringIO.new(unbase64file))
177
+ end
178
+
179
+ end
180
+
181
+ end
182
+ end
@@ -136,7 +136,7 @@ module Deltacloud
136
136
  props = []
137
137
  self.each_property do |p|
138
138
  if p.kind.eql? :fixed
139
- props << { :kind => p.kind, :value => p.value, :name => p.name, :unit => p.unit }
139
+ props << { :kind => p.kind, :value => p.value, :name => p.name, :unit => p.unit }
140
140
  else
141
141
  param = { :operation => "create", :method => "post", :name => p.name }
142
142
  if p.kind.eql? :range
@@ -15,7 +15,8 @@
15
15
  # under the License.
16
16
 
17
17
  require 'deltacloud/helpers/application_helper'
18
+ require 'deltacloud/helpers/json_helper'
18
19
  require 'deltacloud/helpers/conversion_helper'
19
20
  require 'deltacloud/helpers/hardware_profiles_helper'
20
21
 
21
- helpers ApplicationHelper, ConversionHelper, HardwareProfilesHelper
22
+ helpers ApplicationHelper, ConversionHelper, HardwareProfilesHelper, JSONHelper
@@ -22,17 +22,17 @@ module ApplicationHelper
22
22
  include Deltacloud
23
23
 
24
24
  def bread_crumb
25
- s = "<ul class='breadcrumb'><li class='first'><a href='#{url_for('/')}'>&#948</a></li>"
25
+ s = "<ul class='breadcrumb'><li class='first'><a href='#{root_url}'>&#948</a></li>"
26
26
  url = request.path_info.split('?') #remove extra query string parameters
27
27
  levels = url[0].split('/') #break up url into different levels
28
28
  levels.each_with_index do |level, index|
29
29
  unless level.blank?
30
- if index == levels.size-1 ||
31
- (level == levels[levels.size-2] && levels[levels.size-1].to_i > 0)
30
+ next if "/#{level}" == Sinatra::UrlForHelper::DEFAULT_URI_PREFIX
31
+ if index == levels.size-1 || (level == levels[levels.size-2] && levels[levels.size-1].to_i > 0)
32
32
  s += "<li class='subsequent'>#{level.gsub(/_/, ' ')}</li>\n" unless level.to_i > 0
33
33
  else
34
- link = levels.slice(0, index+1).join("/")
35
- s += "<li class='subsequent'><a href=\"#{url_for(link)}\">#{level.gsub(/_/, ' ')}</a></li>\n"
34
+ link = levels.slice(2, index-1).join("/")
35
+ s += "<li class='subsequent'><a href=\"#{api_url_for(link)}\">#{level.gsub(/_/, ' ')}</a></li>\n"
36
36
  end
37
37
  end
38
38
  end
@@ -100,16 +100,18 @@ module ApplicationHelper
100
100
  format.json { convert_to_json(model, @element) }
101
101
  end
102
102
  else
103
- report_error(404, 'not_found')
103
+ report_error(404)
104
104
  end
105
105
  end
106
106
 
107
- def report_error(status, template)
108
- @error = request.env['sinatra.error']
109
- response.status = status
107
+ def report_error(code=nil)
108
+ @error, @code = request.env['sinatra.error'], code
109
+ @code = 500 if not @code and not @error.class.method_defined? :code
110
+ response.status = @code || @error.code
110
111
  respond_to do |format|
111
- format.xml { haml :"errors/#{template}", :layout => false }
112
- format.html { haml :"errors/#{template}", :layout => :error }
112
+ format.xml { haml :"errors/#{@code || @error.code}", :layout => false }
113
+ format.json { json_return_error(@error) }
114
+ format.html { haml :"errors/#{@code || @error.code}", :layout => :error }
113
115
  end
114
116
  end
115
117
 
@@ -119,7 +121,7 @@ module ApplicationHelper
119
121
  # If original instance doesn't include called action
120
122
  # return with 405 error (Method is not Allowed)
121
123
  unless driver.instance_actions_for(original_instance.state).include?(name.to_sym)
122
- return report_error(405, 'not_allowed')
124
+ return report_error(405)
123
125
  end
124
126
 
125
127
  @instance = driver.send(:"#{name}_instance", credentials, params["id"])
@@ -145,14 +147,14 @@ module ApplicationHelper
145
147
  end
146
148
 
147
149
  def render_cdata(text)
148
- "<pem><![CDATA[#{text.strip}]]></pem>"
150
+ "<![CDATA[#{text.strip}]]>"
149
151
  end
150
152
 
151
153
  def link_to_action(action, url, method)
152
154
  capture_haml do
153
155
  haml_tag :form, :method => :post, :action => url, :class => [:link, method] do
154
156
  haml_tag :input, :type => :hidden, :name => '_method', :value => method
155
- haml_tag :button, :type => :submit do
157
+ haml_tag :button, :type => :submit, :'data-ajax' => 'false', :'data-inline' => "true" do
156
158
  haml_concat action
157
159
  end
158
160
  end
@@ -168,25 +170,26 @@ module ApplicationHelper
168
170
  else
169
171
  uri+="?format=#{format}"
170
172
  end
171
- '<a href="%s">%s</a>' % [uri, "#{format}".upcase]
173
+ '<a data-ajax="false" data-icon="grid" href="%s">%s</a>' % [uri, "#{format}".upcase]
172
174
  end
173
175
 
174
176
  def link_to_documentation
175
177
  return '' unless request.env['REQUEST_URI']
176
178
  uri = request.env['REQUEST_URI'].dup
177
- uri.gsub!('/api', '/api/docs/') unless uri.include?("docs") #i.e. if already serving under /api/docs, leave it be
179
+ uri.gsub!(Sinatra::UrlForHelper::DEFAULT_URI_PREFIX,
180
+ api_url_for(:docs)) unless uri.include?("docs") #i.e. if already serving under /api/docs, leave it be
178
181
  '<a href="%s">[ Documentation ]</a>' % uri
179
182
  end
180
183
 
181
184
  def action_url
182
185
  if [:index].include?(@operation.name)
183
- url_for("/api/#{@collection.name.to_s}")
186
+ api_url_for("#{@collection.name.to_s}")
184
187
  elsif [:show, :stop, :start, :reboot, :attach, :detach].include?(@operation.name)
185
- url_for("/api/#{@collection.name.to_s}/:id/#{@operation.name}")
188
+ api_url_for("#{@collection.name.to_s}/:id/#{@operation.name}")
186
189
  elsif [:destroy].include?(@operation.name)
187
- url_for("/api/#{@collection.name.to_s}/:id")
190
+ api_url_for("#{@collection.name.to_s}/:id")
188
191
  else
189
- url_for("/api/#{@collection.name}/#{@operation.name}")
192
+ api_url_for("#{@collection.name}/#{@operation.name}")
190
193
  end
191
194
  end
192
195
 
@@ -216,4 +219,73 @@ module ApplicationHelper
216
219
  end
217
220
  result
218
221
  end
222
+
223
+ def header(title, opts={}, &block)
224
+ opts[:theme] ||= 'b'
225
+ opts[:back] ||= 'true'
226
+ capture_haml do
227
+ haml_tag :div, :'data-role' => :header, :'data-theme' => opts[:theme], :'data-add-back-btn' => opts[:back] do
228
+ haml_tag :a, :'data-rel' => :back do
229
+ haml_concat "Back"
230
+ end if opts[:back] == 'true'
231
+ haml_tag :h1 do
232
+ haml_concat title
233
+ end
234
+ block.call if block_given?
235
+ end
236
+ end
237
+ end
238
+
239
+ def subheader(title, opts={})
240
+ opts[:theme] ||= 'a'
241
+ capture_haml do
242
+ haml_tag :div, :'data-role' => :header, :'data-theme' => opts[:theme] do
243
+ haml_tag :p, :class => 'inner-right' do
244
+ haml_concat title
245
+ end
246
+ end
247
+ end
248
+ end
249
+
250
+ # FIXME: It would be cleaner if we stored the type of address explicitly in
251
+ # public_addresses instead of guessing it; especially since now a RHEV-M
252
+ # vnc address in theory could look like type ipv4.
253
+ #
254
+ # Instead of pushing just the address onto public_addresses, we should
255
+ # just push a pair [type, address], i.e. [:vnc, "172.16.0.1"] or a hash
256
+ # { :vnc => "172.16.0.1" }
257
+ #
258
+ def address_type(address)
259
+ case address
260
+ when /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/; :ipv4
261
+ when /^.*:([\-\d]+)$/; :vnc
262
+ when /^(\S{1,2}:\S{1,2}:\S{1,2}:\S{1,2}:\S{1,2}:\S{1,2})?$/; :mac
263
+ else :hostname
264
+ end
265
+ end
266
+
267
+ def format_address(address, opts={})
268
+ capture_haml do
269
+ haml_tag :address, { :type => address_type(address) }.merge(opts) do
270
+ haml_concat address
271
+ end
272
+ end
273
+ end
274
+
275
+ def translate_error_code(code)
276
+ case code
277
+ when 400; { :message => "Bad Request" }
278
+ when 401; { :message => "Unauthorized" }
279
+ when 403; { :message => "Forbidden" }
280
+ when 404; { :message => "Not Found" }
281
+ when 405; { :message => "Method Not Allowed" }
282
+ when 406; { :message => "Not Acceptable" }
283
+ when 500; { :message => "Internal Server Error" }
284
+ when 502; { :message => "Backend Server Error" }
285
+ end
286
+ end
287
+
288
+ def new_blob_form_url(bucket)
289
+ bucket_url(@bucket.name) + "/" + NEW_BLOB_FORM_ID
290
+ end
219
291
  end
@@ -17,7 +17,8 @@ include Deltacloud
17
17
  begin
18
18
  require 'eventmachine'
19
19
  #--
20
- # based on the example from http://macournoyer.com/blog/2009/06/04/pusher-and-async-with-thin/
20
+ # based on the example from
21
+ # http://macournoyer.com/blog/2009/06/04/pusher-and-async-with-thin/
21
22
  #--
22
23
  class BlobStream
23
24
  AsyncResponse = [-1, {}, []].freeze
@@ -30,13 +31,13 @@ begin
30
31
  EM.next_tick { env['async.callback'].call [200, {
31
32
  'Content-Type' => "#{params['content_type']}",
32
33
  'Content-Disposition' => params["content_disposition"],
33
- 'Content-Length' => "#{params['content_length']}"}, body]
34
+ 'Content-Length' => "#{params['content_length']}"}, body]
34
35
  }
35
- #call the driver from here. the driver method yields for every chunk of blob it receives. We then
36
- #use body.call to write that chunk as received.
36
+ #call the driver from here. the driver method yields for every chunk
37
+ #of blob it receives. Then use body.call to write that chunk as received.
37
38
  driver.blob_data(credentials, params[:bucket], params[:blob], params) {|chunk| body.call ["#{chunk}"]} #close blob_data block
38
39
  body.succeed
39
- AsyncResponse # Tells Thin to not close the connection and continue it's work on other request
40
+ AsyncResponse # Tell Thin to not close connection & work other requests
40
41
  end
41
42
  end
42
43
 
@@ -64,18 +65,165 @@ end
64
65
 
65
66
  class Hash
66
67
 
67
- def gsub_keys(pattern, replacement)
68
- rgx_pattern = Regexp.compile(pattern, true)
68
+ def gsub_keys(rgx_pattern, replacement)
69
69
  remove = []
70
70
  self.each_key do |key|
71
71
  if key.to_s.match(rgx_pattern)
72
- new_key = key.to_s.gsub(rgx_pattern, replacement)
72
+ new_key = key.to_s.gsub(rgx_pattern, replacement).downcase
73
73
  self[new_key] = self[key]
74
74
  remove << key
75
- end #key.match
76
- end # each_key do
75
+ end
76
+ end
77
77
  #remove the original keys
78
78
  self.delete_if{|k,v| remove.include?(k)}
79
- end #def
79
+ end
80
+
81
+ end
82
+
83
+ module BlobHelper
84
+
85
+ def self.extract_blob_metadata_hash(env_hash)
86
+ meta_array = env_hash.select{|k,v| k.match(/^HTTP[-_]X[-_]Deltacloud[-_]Blobmeta[-_]/i)}
87
+ metadata = meta_array.inject({}){ |result, array| result[array.first.upcase] = array.last; result}
88
+ metadata
89
+ end
90
+
91
+ DELTACLOUD_BLOBMETA_HEADER = /HTTP[-_]X[-_]Deltacloud[-_]Blobmeta[-_]/i
92
+
93
+ #e.g. from HTTP-X-Deltacloud-Blobmeta-FOO:BAR to amz-meta-FOO:BAR
94
+ def self.rename_metadata_headers(metadata, rename_to)
95
+ metadata.gsub_keys(DELTACLOUD_BLOBMETA_HEADER, rename_to)
96
+ end
97
+
98
+ end
99
+
100
+ #Monkey patch for streaming blobs:
101
+ # Normally a client will upload a blob to deltacloud and thin will put
102
+ # this into a tempfile. Then deltacloud would stream up to the provider:
103
+ # i.e. client =-->>TEMP_FILE-->> deltacloud =-->>STREAM-->> provider
104
+ # Instead we want to recognise that this is a 'PUT blob' operation and
105
+ # start streaming to the provider as the request is received:
106
+ # i.e. client =-->>STREAM-->> deltacloud =-->>STREAM-->> provider
107
+ module Thin
108
+ class Request
109
+
110
+ alias_method :move_body_to_tempfile_orig, :move_body_to_tempfile if defined?(Thin::Response)
111
+ private
112
+ def move_body_to_tempfile
113
+ if BlobStreamIO::is_put_blob(self)
114
+ @body = BlobStreamIO.new(self)
115
+ else
116
+ move_body_to_tempfile_orig
117
+ end
118
+ end
119
+
120
+ end
121
+ end
122
+
123
+ require 'net/http'
124
+ #monkey patch for Net:HTTP
125
+ module Net
126
+ class HTTP
127
+
128
+ alias :request_orig :request
129
+
130
+ def request(req, body = nil, blob_stream = nil, &block)
131
+ unless blob_stream
132
+ return request_orig(req, body, &block)
133
+ end
134
+ @blob_req = req
135
+ do_start #start the connection
136
+
137
+ req.set_body_internal body
138
+ begin_transport req
139
+ req.write_header_m @socket,@curr_http_version, edit_path(req.path)
140
+ @socket
141
+ end
142
+
143
+ class Put < HTTPRequest
144
+ def write_header_m(sock, ver, path)
145
+ write_header(sock, ver, path)
146
+ end
147
+ end
148
+
149
+ def end_request
150
+ begin
151
+ res = HTTPResponse.read_new(@socket)
152
+ end while res.kind_of?(HTTPContinue)
153
+ res.reading_body(@socket, @blob_req.response_body_permitted?) {
154
+ yield res if block_given? }
155
+ end_transport @blob_req, res
156
+ do_finish
157
+ res
158
+ end
159
+ end
160
+
161
+ end
162
+
163
+ require 'base64'
164
+ class BlobStreamIO
165
+
166
+ attr_accessor :size, :provider, :sock
167
+
168
+ def initialize(request)
169
+ @client_request = request
170
+ @size = 0
171
+ bucket, blob = parse_bucket_blob(request.env["PATH_INFO"])
172
+ user, password = parse_credentials(request.env['HTTP_AUTHORIZATION'])
173
+ content_type = request.env['CONTENT_TYPE'] || ""
174
+ #deal with blob_metadata: (X-Deltacloud-Blobmeta-name: value)
175
+ user_meta = BlobHelper::extract_blob_metadata_hash(request.env)
176
+ @content_length = request.env['CONTENT_LENGTH']
177
+ @http, provider_request = driver.blob_stream_connection({:user=>user,
178
+ :password=>password, :bucket=>bucket, :blob=>blob, :metadata=> user_meta,
179
+ :content_type=>content_type, :content_length=>@content_length })
180
+ @content_length = @content_length.to_i #for comparison of size in '<< (data)'
181
+ @sock = @http.request(provider_request, nil, true)
182
+ end
183
+
184
+ def << (data)
185
+ @sock.write(data)
186
+ @size += data.length
187
+ if (@size >= @content_length)
188
+ result = @http.end_request
189
+ if result.is_a?(Net::HTTPSuccess)
190
+ @client_request.env["BLOB_SUCCESS"] = "true"
191
+ else
192
+ @client_request.env["BLOB_FAIL"] = result.body
193
+ end
194
+ end
195
+ end
196
+
197
+ def rewind
198
+ end
199
+
200
+ #use the Request.env hash (populated by the ThinParser) to determine whether
201
+ #this is a post blob operation. By definition, only get here with a body of
202
+ # > 112kbytes - thin/lib/thin/request.rb:12 MAX_BODY = 1024 * (80 + 32)
203
+ def self.is_put_blob(request = nil)
204
+ path = request.env['PATH_INFO']
205
+ method = request.env['REQUEST_METHOD']
206
+ if ( path =~ /^#{Regexp.escape(Sinatra::UrlForHelper::DEFAULT_URI_PREFIX)}\/buckets/ && method == 'PUT' )
207
+ return true
208
+ else
209
+ return false
210
+ end
211
+ end
80
212
 
81
- end #class
213
+ private
214
+
215
+ def parse_bucket_blob(request_string)
216
+ array = request_string.split("/")
217
+ blob = array.pop
218
+ bucket = array.pop
219
+ return bucket, blob
220
+ end
221
+
222
+ def parse_credentials(request_string)
223
+ decoded = Base64.decode64(request_string.split('Basic ').last)
224
+ key = decoded.split(':').first
225
+ pass = decoded.split(':').last
226
+ return key, pass
227
+ end
228
+
229
+ end