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
@@ -19,7 +19,7 @@ require 'deltacloud/base_driver'
19
19
  module ConversionHelper
20
20
 
21
21
  def convert_to_json(type, obj)
22
- if ( [ :flavor, :account, :image, :realm, :instance, :storage_volume, :storage_snapshot, :hardware_profile ].include?( type ) )
22
+ if ( [ :image, :realm, :instance, :storage_volume, :storage_snapshot, :hardware_profile, :key, :bucket, :blob, :firewall, :load_balancer, :address ].include?( type ) )
23
23
  if Array.eql?(obj.class)
24
24
  data = obj.collect do |o|
25
25
  o.to_hash.merge({ :href => self.send(:"#{type}_url", type.eql?(:hardware_profile) ? o.name : o.id ) })
@@ -27,7 +27,11 @@ module ConversionHelper
27
27
  type = type.to_s.pluralize
28
28
  else
29
29
  data = obj.to_hash
30
- data.merge!({ :href => self.send(:"#{type}_url", data[:id]) })
30
+ if type == :blob
31
+ data.merge!({ :href => self.send(:"bucket_url", "#{data[:bucket]}/#{data[:id]}" ) })
32
+ else
33
+ data.merge!({ :href => self.send(:"#{type}_url", data[:id]) })
34
+ end
31
35
  end
32
36
  return { :"#{type}" => data }.to_json
33
37
  end
@@ -0,0 +1,31 @@
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
+ module JSONHelper
17
+
18
+ def json_features_for_entrypoint(entrypoint)
19
+ features = driver.features(entrypoint.first).collect { |feature| feature.name }
20
+ features.empty? ? {} : { :features => features }
21
+ end
22
+
23
+ def json_return_error(error)
24
+ error_output=Hash.new
25
+ error_output[:url] =request.env['REQUEST_URI']
26
+ error_output[:status] =response.status
27
+ error_output[:message]=error.message if error
28
+ error_output.to_json
29
+ end
30
+
31
+ end
@@ -0,0 +1,28 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one or more
3
+ # contributor license agreements. See the NOTICE file distributed with
4
+ # this work for additional information regarding copyright ownership. The
5
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance with the
7
+ # License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ # License for the specific language governing permissions and limitations
15
+ # under the License.
16
+
17
+ class Address < BaseModel
18
+ attr_accessor :instance_id
19
+
20
+ def initialize(init=nil)
21
+ super(init)
22
+ end
23
+
24
+ def associated?
25
+ true unless self.instance_id.nil?
26
+ end
27
+
28
+ end
@@ -46,7 +46,11 @@ class BaseModel
46
46
 
47
47
  def to_hash
48
48
  out = {}
49
- self.attributes.each { |attribute| out.merge!({ attribute => self.send(:"#{attribute}") } ) }
49
+ self.attributes.each do |attribute|
50
+ if self.send(:"#{attribute}")
51
+ out.merge!({ attribute => self.send(:"#{attribute}") } )
52
+ end
53
+ end
50
54
  out
51
55
  end
52
56
 
@@ -15,7 +15,7 @@
15
15
  # under the License.
16
16
 
17
17
  class Blob < BaseModel
18
-
18
+
19
19
  #already has an id from basemodel (for the key)
20
20
  attr_accessor :bucket
21
21
  attr_accessor :content_length
@@ -20,4 +20,14 @@ class Bucket < BaseModel
20
20
  attr_accessor :size
21
21
  attr_accessor :blob_list
22
22
 
23
+ alias :to_hash_original :to_hash
24
+
25
+ def to_hash
26
+ h = self.to_hash_original
27
+ unless blob_list.nil?
28
+ h[:blob_list] = self.blob_list.collect { |blob| { :id => blob,
29
+ :href => "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/#{self.id}/#{blob}"}}
30
+ end
31
+ return h
32
+ end
23
33
  end
@@ -0,0 +1,22 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one or more
3
+ # contributor license agreements. See the NOTICE file distributed with
4
+ # this work for additional information regarding copyright ownership. The
5
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance with the
7
+ # License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ # License for the specific language governing permissions and limitations
15
+ # under the License.
16
+
17
+ class Firewall < BaseModel
18
+ attr_accessor :name
19
+ attr_accessor :description
20
+ attr_accessor :owner_id
21
+ attr_accessor :rules
22
+ end
@@ -0,0 +1,23 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one or more
3
+ # contributor license agreements. See the NOTICE file distributed with
4
+ # this work for additional information regarding copyright ownership. The
5
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance with the
7
+ # License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ # License for the specific language governing permissions and limitations
15
+ # under the License.
16
+
17
+ class FirewallRule < BaseModel
18
+ attr_accessor :allow_protocol # tcp/udp/icmp
19
+ attr_accessor :port_from
20
+ attr_accessor :port_to
21
+ attr_accessor :sources
22
+ attr_accessor :direction #ingress egress
23
+ end
@@ -23,4 +23,16 @@ class Image < BaseModel
23
23
  attr_accessor :architecture
24
24
  attr_accessor :state
25
25
 
26
+ alias :to_hash_original :to_hash
27
+
28
+ def to_hash
29
+ h = self.to_hash_original
30
+ h.merge({
31
+ :actions => [ :create_instance => {
32
+ :method => 'post',
33
+ :href => "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/instances;image_id=#{self.id}"
34
+ }]
35
+ })
36
+ end
37
+
26
38
  end
@@ -16,6 +16,8 @@
16
16
 
17
17
  class Instance < BaseModel
18
18
 
19
+ include ApplicationHelper
20
+
19
21
  attr_accessor :owner_id
20
22
  attr_accessor :image_id
21
23
  attr_accessor :name
@@ -31,6 +33,7 @@ class Instance < BaseModel
31
33
  attr_accessor :username
32
34
  attr_accessor :password
33
35
  attr_accessor :create_image
36
+ attr_accessor :firewalls
34
37
 
35
38
  def can_create_image?
36
39
  self.create_image
@@ -59,13 +62,28 @@ class Instance < BaseModel
59
62
  if name =~ /is_(\w+)\?/
60
63
  return true if self.state.downcase.eql?($1)
61
64
  else
62
- raise NoMethodError
65
+ raise NoMethodError.new(name.to_s)
63
66
  end
64
67
  end
65
68
 
66
69
  def authn_feature_failed?
67
70
  return true unless authn_error.nil?
68
71
  end
69
-
72
+
73
+ alias :to_hash_original :to_hash
74
+
75
+ def to_hash
76
+ h = self.to_hash_original
77
+ h[:public_addresses] = h[:public_addresses].collect do |address|
78
+ { :address => { :type => address_type(address), :value => address } }
79
+ end
80
+ h[:actions] = self.actions.collect do |action|
81
+ { :"#{action}" => {
82
+ :method => collections[:instances].operations[action.to_sym].method,
83
+ :href => collections[:instances].operations[action.to_sym].path.gsub(':id', self.id)
84
+ }}
85
+ end
86
+ h
87
+ end
70
88
 
71
89
  end
@@ -41,7 +41,7 @@ class Key < BaseModel
41
41
  # NOTE: This is a fake PEM file, it will not work against SSH
42
42
  def self.generate_mock_pem
43
43
  chars = (('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a + %w(= / + ))
44
- pem_material = (1..21).map do
44
+ pem_material = (1..21).map do
45
45
  (1..75).collect{|a| chars[rand(chars.size)] }.join
46
46
  end.join("\n") + "\n" + (1..68).collect{|a| chars[rand(chars.size)] }.join
47
47
  "-----BEGIN RSA PRIVATE KEY-----\n"+pem_material+"-----END RSA PRIVATE KEY-----"
@@ -28,11 +28,11 @@ module Deltacloud
28
28
  super
29
29
  end
30
30
  end
31
-
31
+
32
32
  class InstanceSSHError < RunnerError; end
33
33
 
34
34
  def self.execute(command, opts={})
35
-
35
+
36
36
  if opts[:credentials] and (not opts[:credentials][:password] and not opts[:private_key])
37
37
  raise RunnerError::new("Either password or key must be specified")
38
38
  end
@@ -118,7 +118,7 @@ module Deltacloud
118
118
  end
119
119
 
120
120
  class Response
121
-
121
+
122
122
  attr_reader :body
123
123
  attr_reader :ssh
124
124
 
@@ -24,7 +24,7 @@ module Deltacloud::Validation
24
24
  end
25
25
 
26
26
  def name
27
- param.name
27
+ param.name if @param
28
28
  end
29
29
  end
30
30
 
@@ -35,12 +35,8 @@ module Deltacloud::Validation
35
35
  @name = args[0]
36
36
  @klass = args[1] || :string
37
37
  @type = args[2] || :optional
38
- if args[3] and args[3].class.eql?(String)
39
- @description = args[3]
40
- @options = []
41
- end
42
- @options ||= args[3] || []
43
- @description ||= args[4] || ''
38
+ @options = args[3] || []
39
+ @description = args[4] || ''
44
40
  end
45
41
 
46
42
  def required?
@@ -32,7 +32,13 @@ module Deltacloud
32
32
  DRIVER=ENV['API_DRIVER'] ? ENV['API_DRIVER'].to_sym : :mock
33
33
 
34
34
  def driver_config
35
- YAML::load(File.read(File.join(File.dirname(__FILE__), '..', 'config', 'drivers.yaml')))
35
+ if Thread::current[:drivers].nil?
36
+ Thread::current[:drivers] = {}
37
+ Dir[File.join(File::dirname(__FILE__), '..', 'config', 'drivers', '*.yaml')].each do |driver_file|
38
+ Thread::current[:drivers].merge!(YAML::load(File::read(driver_file)))
39
+ end
40
+ end
41
+ Thread::current[:drivers]
36
42
  end
37
43
 
38
44
  def driver_symbol
@@ -0,0 +1,34 @@
1
+ # This code was originaly copied from Rack::BodyProxy
2
+ # https://github.com/rack/rack/blob/master/lib/rack/body_proxy.rb
3
+ #
4
+ # Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
5
+
6
+ module Rack
7
+ class BodyProxy
8
+ def initialize(body, &block)
9
+ @body, @block, @closed = body, block, false
10
+ end
11
+
12
+ def respond_to?(*args)
13
+ super or @body.respond_to?(*args)
14
+ end
15
+
16
+ def close
17
+ raise IOError, "closed stream" if @closed
18
+ begin
19
+ @body.close if @body.respond_to? :close
20
+ ensure
21
+ @block.call
22
+ @closed = true
23
+ end
24
+ end
25
+
26
+ def closed?
27
+ @closed
28
+ end
29
+
30
+ def method_missing(*args, &block)
31
+ @body.__send__(*args, &block)
32
+ end
33
+ end
34
+ end
@@ -42,6 +42,11 @@ module Sinatra
42
42
 
43
43
  private
44
44
  def credentials!
45
+ if ENV["API_USER"] && ENV["API_PASSWORD"]
46
+ @user = ENV["API_USER"]
47
+ @password = ENV["API_PASSWORD"]
48
+ @provided = true
49
+ end
45
50
  unless provided?
46
51
  auth = Rack::Auth::Basic::Request.new(@app.request.env)
47
52
  unless auth.provided? && auth.basic? && auth.credentials
@@ -23,18 +23,32 @@ module Sinatra
23
23
 
24
24
  module Rabbit
25
25
 
26
- class DuplicateParamException < Exception; end
27
- class DuplicateOperationException < Exception; end
28
- class DuplicateCollectionException < Exception; end
29
- class UnsupportedCollectionException < Exception; end
26
+ def self.routes
27
+ @routes ||= []
28
+ end
29
+
30
+ class DuplicateParamException < Deltacloud::ExceptionHandler::DeltacloudException; end
31
+ class DuplicateOperationException < Deltacloud::ExceptionHandler::DeltacloudException; end
32
+ class DuplicateCollectionException < Deltacloud::ExceptionHandler::DeltacloudException; end
33
+ class UnsupportedCollectionException < Deltacloud::ExceptionHandler::DeltacloudException
34
+ def initialize
35
+ # The server understood the request, but is refusing to fulfill it. Authorization will not help and the request
36
+ # SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make public why the request
37
+ # has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. If the server does not wish
38
+ # to make this information available to the client, the status code 404 (Not Found) can be used instead.
39
+ super(403, 'UnsupportedCollection', "Requested collection is not supported for current provider", [])
40
+ end
41
+ end
30
42
 
31
43
  class Operation
32
- attr_reader :name, :method, :collection
44
+ attr_reader :name, :method, :collection, :member
33
45
 
34
46
  include ::Deltacloud::BackendCapability
35
47
  include ::Deltacloud::Validation
48
+ include ::ApplicationHelper
36
49
 
37
50
  STANDARD = {
51
+ :new => { :method => :get, :member => false, :form => true },
38
52
  :index => { :method => :get, :member => false },
39
53
  :show => { :method => :get, :member => true },
40
54
  :create => { :method => :post, :member => false },
@@ -63,6 +77,10 @@ module Sinatra
63
77
  STANDARD.keys.include?(name)
64
78
  end
65
79
 
80
+ def form?
81
+ STANDARD[name] and STANDARD[name][:form]
82
+ end
83
+
66
84
  def description(text="")
67
85
  return @description if text.blank?
68
86
  @description = text
@@ -70,7 +88,8 @@ module Sinatra
70
88
 
71
89
  def generate_documentation
72
90
  coll, oper = @collection, self
73
- ::Sinatra::Application.get("/api/docs/#{@collection.name}/#{@name}") do
91
+ Rabbit::routes << [:get, "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/docs/#{@collection.name}/#{@name}"]
92
+ ::Sinatra::Application.get("#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/docs/#{@collection.name}/#{@name}") do
74
93
  @collection, @operation = coll, oper
75
94
  @features = driver.features_for_operation(coll.name, oper.name)
76
95
  respond_to do |format|
@@ -82,7 +101,8 @@ module Sinatra
82
101
 
83
102
  def generate_options
84
103
  current_operation = self
85
- ::Sinatra::Application.options("/api/#{current_operation.collection.name}/#{current_operation.name}") do
104
+ Rabbit::routes << [:options, "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/#{current_operation.collection.name}/#{current_operation.name}"]
105
+ ::Sinatra::Application.options("#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/#{current_operation.collection.name}/#{current_operation.name}") do
86
106
  required_params = current_operation.effective_params(driver).collect do |name, validation|
87
107
  name.to_s if validation.type.eql?(:required)
88
108
  end.compact.join(',')
@@ -105,26 +125,25 @@ module Sinatra
105
125
  end
106
126
  end
107
127
 
108
- def prefix
109
- # FIXME: Make the /api prefix configurable
110
- "/api"
111
- end
112
-
113
128
  def path(args = {})
114
- l_prefix = args[:prefix] || prefix
115
129
  if @member
116
130
  if standard?
117
- "#{l_prefix}/#{@collection.name}/:id"
131
+ "#{@collection.name}/:id"
118
132
  else
119
- "#{l_prefix}/#{@collection.name}/:id/#{name}"
133
+ "#{@collection.name}/:id/#{name}"
120
134
  end
121
135
  else
122
- "#{l_prefix}/#{@collection.name}"
136
+ if form?
137
+ "#{@collection.name}/#{name}"
138
+ else
139
+ "#{@collection.name}"
140
+ end
123
141
  end
124
142
  end
125
143
 
126
144
  def generate
127
- ::Sinatra::Application.send(@method, path, {}, &@control)
145
+ Rabbit::routes << [@method, "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/#{path}"]
146
+ ::Sinatra::Application.send(@method, "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/#{path}", {}, &@control)
128
147
  # Set up some Rails-like URL helpers
129
148
  if name == :index
130
149
  gen_route "#{@collection.name}_url"
@@ -159,12 +178,12 @@ module Sinatra
159
178
  if @member
160
179
  ::Sinatra::Application.send(:define_method, name) do |id, *args|
161
180
  url = query_url(route_url, args[0])
162
- url_for url.gsub(/:id/, id.to_s), :full
181
+ api_url_for url.gsub(/:id/, id.to_s), :full
163
182
  end
164
183
  else
165
184
  ::Sinatra::Application.send(:define_method, name) do |*args|
166
185
  url = query_url(route_url, args[0])
167
- url_for url, :full
186
+ api_url_for url, :full
168
187
  end
169
188
  end
170
189
  end
@@ -206,7 +225,8 @@ module Sinatra
206
225
 
207
226
  def generate_head
208
227
  current_collection = self
209
- ::Sinatra::Application.head("/api/#{name}") do
228
+ Rabbit::routes << [:head, "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/#{name}"]
229
+ ::Sinatra::Application.head("#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/#{name}") do
210
230
  methods_allowed = current_collection.operations.collect { |o| o[1].method.to_s.upcase }.uniq.join(',')
211
231
  headers 'Allow' => "HEAD,OPTIONS,#{methods_allowed}"
212
232
  [200, '']
@@ -215,7 +235,8 @@ module Sinatra
215
235
 
216
236
  def generate_options
217
237
  current_collection = self
218
- ::Sinatra::Application.options("/api/#{name}") do
238
+ Rabbit::routes << [:options, "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/#{name}"]
239
+ ::Sinatra::Application.options("#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/#{name}") do
219
240
  operations_allowed = current_collection.operations.collect { |o| o[0] }.join(',')
220
241
  headers 'X-Operations-Allowed' => operations_allowed
221
242
  [200, '']
@@ -224,7 +245,8 @@ module Sinatra
224
245
 
225
246
  def generate_documentation
226
247
  coll = self
227
- ::Sinatra::Application.get("/api/docs/#{@name}") do
248
+ Rabbit::routes << [:get, "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/docs/#{@name}"]
249
+ ::Sinatra::Application.get("#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/docs/#{@name}") do
228
250
  coll.check_supported(driver)
229
251
  @collection = coll
230
252
  @operations = coll.operations
@@ -252,29 +274,30 @@ module Sinatra
252
274
  # This also defines a helper method like show_instance_url that returns
253
275
  # the URL to this operation (in request context)
254
276
  def operation(name, opts = {}, &block)
255
- raise DuplicateOperationException if @operations[name]
277
+ if @operations.keys.include?(name)
278
+ raise DuplicateOperationException::new(500, "DuplicateOperation", "Operation #{name} is already defined", [])
279
+ end
256
280
  @operations[name] = Operation.new(self, name, opts, &block)
257
281
  end
258
282
 
259
283
  def generate
260
- operations.values.each { |op| op.generate }
284
+ operations.values.reject { |op| op.member }.each { |o| o.generate }
285
+ operations.values.select { |op| op.member }.each { |o| o.generate }
261
286
  app = ::Sinatra::Application
262
287
  collname = name # Work around Ruby's weird scoping/capture
263
288
  app.send(:define_method, "#{name.to_s.singularize}_url") do |id|
264
- url_for "/api/#{collname}/#{id}", :full
289
+ api_url_for "#{collname}/#{id}", :full
265
290
  end
266
-
267
291
  if index_op = operations[:index]
268
292
  app.send(:define_method, "#{name}_url") do
269
- url_for index_op.path.gsub(/\/\?$/,''), :full
293
+ api_url_for index_op.path.gsub(/\/\?$/,''), :full
270
294
  end
271
295
  end
272
296
  end
273
297
 
274
298
  def check_supported(driver)
275
299
  unless global? || driver.has_collection?(@name)
276
- raise UnsupportedCollectionException,
277
- "Collection #{@name} not supported by this driver"
300
+ raise UnsupportedCollectionException
278
301
  end
279
302
  end
280
303
  end
@@ -295,7 +318,7 @@ module Sinatra
295
318
  end
296
319
 
297
320
  # Generate a root route for API docs
298
- get '/api/docs\/?' do
321
+ get "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/docs\/?" do
299
322
  respond_to do |format|
300
323
  format.html { haml :'docs/index' }
301
324
  format.xml { haml :'docs/index' }
@@ -314,7 +337,7 @@ module Sinatra
314
337
  collections.values.select { |coll|
315
338
  coll.global? || driver.has_collection?(coll.name)
316
339
  }.inject([]) do |m, coll|
317
- url = url_for coll.operations[:index].path, :full
340
+ url = api_url_for coll.operations[:index].path, :full
318
341
  m << [ coll.name, url ]
319
342
  end
320
343
  end