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.
- data/LICENSE +145 -0
- data/NOTICE +10 -1
- data/Rakefile +50 -2
- data/bin/deltacloudd +111 -14
- data/config/addresses.xml +14 -0
- data/config/condor.yaml +30 -0
- data/config/drivers/azure.yaml +3 -0
- data/config/drivers/condor.yaml +3 -0
- data/config/{drivers.yaml → drivers/ec2.yaml} +5 -34
- data/config/drivers/eucalyptus.yaml +8 -0
- data/config/drivers/gogrid.yaml +3 -0
- data/config/drivers/mock.yaml +3 -0
- data/config/drivers/opennebula.yaml +4 -0
- data/config/drivers/rackspace.yaml +3 -0
- data/config/drivers/rhevm.yaml +3 -0
- data/config/drivers/rimuhosting.yaml +3 -0
- data/config/drivers/sbc.yaml +2 -0
- data/config/drivers/terremark.yaml +3 -0
- data/config/drivers/vsphere.yaml +8 -0
- data/deltacloud-core.gemspec +13 -5
- data/deltacloud.rb +4 -2
- data/lib/deltacloud/backend_capability.rb +2 -2
- data/lib/deltacloud/base_driver/base_driver.rb +23 -52
- data/lib/deltacloud/base_driver/exceptions.rb +168 -0
- data/lib/deltacloud/base_driver/features.rb +31 -12
- data/lib/deltacloud/base_driver/mock_driver.rb +2 -1
- data/lib/deltacloud/core_ext/string.rb +2 -0
- data/lib/deltacloud/drivers/azure/azure_driver.rb +5 -5
- data/lib/deltacloud/drivers/condor/condor_client.rb +273 -0
- data/lib/deltacloud/drivers/condor/condor_driver.rb +236 -0
- data/lib/deltacloud/drivers/condor/ip_agents/confserver.rb +75 -0
- data/lib/deltacloud/drivers/condor/ip_agents/default.rb +84 -0
- data/lib/deltacloud/drivers/ec2/ec2_driver.rb +326 -95
- data/lib/deltacloud/drivers/ec2/ec2_mock_driver.rb +3 -3
- data/lib/deltacloud/drivers/eucalyptus/eucalyptus_driver.rb +40 -8
- data/lib/deltacloud/drivers/gogrid/gogrid_client.rb +7 -7
- data/lib/deltacloud/drivers/gogrid/gogrid_driver.rb +42 -25
- data/lib/deltacloud/drivers/mock/data/{buckets/blobs → blobs}/blob1.yml +6 -4
- data/lib/deltacloud/drivers/mock/data/{buckets/blobs → blobs}/blob2.yml +7 -5
- data/lib/deltacloud/drivers/mock/data/{buckets/blobs → blobs}/blob3.yml +6 -4
- data/lib/deltacloud/drivers/mock/data/{buckets/blobs → blobs}/blob4.yml +6 -4
- data/lib/deltacloud/drivers/mock/data/{buckets/blobs → blobs}/blob5.yml +6 -4
- data/lib/deltacloud/drivers/mock/data/buckets/bucket1.yml +7 -1
- data/lib/deltacloud/drivers/mock/data/buckets/bucket2.yml +6 -1
- data/lib/deltacloud/drivers/mock/data/images/img1.yml +6 -2
- data/lib/deltacloud/drivers/mock/data/images/img2.yml +6 -2
- data/lib/deltacloud/drivers/mock/data/images/img3.yml +6 -2
- data/lib/deltacloud/drivers/mock/data/instances/inst0.yml +11 -10
- data/lib/deltacloud/drivers/mock/data/instances/inst1.yml +14 -7
- data/lib/deltacloud/drivers/mock/data/instances/inst2.yml +14 -7
- data/lib/deltacloud/drivers/mock/data/storage_snapshots/snap1.yml +3 -2
- data/lib/deltacloud/drivers/mock/data/storage_snapshots/snap2.yml +3 -2
- data/lib/deltacloud/drivers/mock/data/storage_snapshots/snap3.yml +3 -2
- data/lib/deltacloud/drivers/mock/data/storage_volumes/vol1.yml +4 -3
- data/lib/deltacloud/drivers/mock/data/storage_volumes/vol2.yml +4 -3
- data/lib/deltacloud/drivers/mock/data/storage_volumes/vol3.yml +4 -3
- data/lib/deltacloud/drivers/mock/mock_client.rb +101 -0
- data/lib/deltacloud/drivers/mock/mock_driver.rb +367 -429
- data/lib/deltacloud/drivers/opennebula/opennebula_driver.rb +6 -0
- data/lib/deltacloud/drivers/rackspace/rackspace_driver.rb +59 -9
- data/lib/deltacloud/drivers/rhevm/rhevm_client.rb +62 -8
- data/lib/deltacloud/drivers/rhevm/rhevm_driver.rb +100 -45
- data/lib/deltacloud/drivers/rimuhosting/rimuhosting_client.rb +3 -2
- data/lib/deltacloud/drivers/rimuhosting/rimuhosting_driver.rb +8 -11
- data/lib/deltacloud/drivers/sbc/sbc_client.rb +6 -6
- data/lib/deltacloud/drivers/sbc/sbc_driver.rb +16 -0
- data/lib/deltacloud/drivers/terremark/terremark_driver.rb +17 -12
- data/lib/deltacloud/drivers/vsphere/vsphere_client.rb +140 -0
- data/lib/deltacloud/drivers/vsphere/vsphere_driver.rb +405 -0
- data/lib/deltacloud/drivers/vsphere/vsphere_filemanager.rb +182 -0
- data/lib/deltacloud/hardware_profile.rb +1 -1
- data/lib/deltacloud/helpers.rb +2 -1
- data/lib/deltacloud/helpers/application_helper.rb +92 -20
- data/lib/deltacloud/helpers/blob_stream.rb +160 -12
- data/lib/deltacloud/helpers/conversion_helper.rb +6 -2
- data/lib/deltacloud/helpers/json_helper.rb +31 -0
- data/lib/deltacloud/models/address.rb +28 -0
- data/lib/deltacloud/models/base_model.rb +5 -1
- data/lib/deltacloud/models/blob.rb +1 -1
- data/lib/deltacloud/models/bucket.rb +10 -0
- data/lib/deltacloud/models/firewall.rb +22 -0
- data/lib/deltacloud/models/firewall_rule.rb +23 -0
- data/lib/deltacloud/models/image.rb +12 -0
- data/lib/deltacloud/models/instance.rb +20 -2
- data/lib/deltacloud/models/key.rb +1 -1
- data/lib/deltacloud/runner.rb +3 -3
- data/lib/deltacloud/validation.rb +3 -7
- data/lib/drivers.rb +7 -1
- data/lib/sinatra/body_proxy.rb +34 -0
- data/lib/sinatra/lazy_auth.rb +5 -0
- data/lib/sinatra/rabbit.rb +54 -31
- data/lib/sinatra/rack_accept.rb +157 -0
- data/lib/sinatra/rack_date.rb +38 -0
- data/lib/sinatra/rack_etag.rb +2 -3
- data/lib/sinatra/rack_matrix_params.rb +51 -29
- data/lib/sinatra/rack_runtime.rb +1 -1
- data/lib/sinatra/rack_syslog.rb +86 -0
- data/lib/sinatra/url_for.rb +14 -1
- data/public/images/address.png +0 -0
- data/public/images/balancer.png +0 -0
- data/public/images/blob.png +0 -0
- data/public/images/bucket.png +0 -0
- data/public/images/cloud.png +0 -0
- data/public/images/firewall.png +0 -0
- data/public/images/image.png +0 -0
- data/public/images/key.png +0 -0
- data/public/images/machine.png +0 -0
- data/public/images/profile.png +0 -0
- data/public/images/realm.png +0 -0
- data/public/images/snapshot.png +0 -0
- data/public/images/volume.png +0 -0
- data/public/javascripts/application.js +119 -16
- data/public/javascripts/jquery.min.js +18 -0
- data/public/javascripts/jquery.mobile-1.0b1.min.js +146 -0
- data/public/stylesheets/compiled/application.css +8 -0
- data/public/stylesheets/images/ajax-loader.png +0 -0
- data/public/{images → stylesheets/images}/bread-bg.png +0 -0
- data/public/{images → stylesheets/images}/error.png +0 -0
- data/public/{images → stylesheets/images}/grid.png +0 -0
- data/public/stylesheets/images/icon-search-black.png +0 -0
- data/public/stylesheets/images/icons-18-black.png +0 -0
- data/public/stylesheets/images/icons-18-white.png +0 -0
- data/public/stylesheets/images/icons-36-black.png +0 -0
- data/public/stylesheets/images/icons-36-white.png +0 -0
- data/public/{images → stylesheets/images}/logo-wide.png +0 -0
- data/public/{images → stylesheets/images}/pending.png +0 -0
- data/public/{images → stylesheets/images}/rails.png +0 -0
- data/public/{images → stylesheets/images}/running.png +0 -0
- data/public/{images → stylesheets/images}/stopped.png +0 -0
- data/public/{images → stylesheets/images}/topbar-bg.png +0 -0
- data/public/stylesheets/jquery.mobile-1.0b1.min.css +8 -0
- data/public/stylesheets/new.css +53 -0
- data/server.rb +487 -175
- data/support/condor/bash/cached_images.sh +8 -0
- data/support/condor/bash/cloud_exit_hook.sh +17 -0
- data/support/condor/bash/cloud_functions +175 -0
- data/support/condor/bash/cloud_prepare_hook.sh +20 -0
- data/support/condor/bash/libvirt_cloud_script.sh +13 -0
- data/support/condor/config/50condor_cloud.config +37 -0
- data/support/condor/config/50condor_cloud_node.config +37 -0
- data/support/condor/config/condor-cloud +2 -0
- data/support/condor/config/condor_config.local +44 -0
- data/support/fedora/deltacloud-core +48 -26
- data/support/fedora/deltacloud-core-config +26 -0
- data/support/fedora/deltacloud-core.spec +314 -68
- data/support/fedora/deltacloudd-fedora +5 -0
- data/tests/common.rb +34 -4
- data/tests/drivers/mock/api_test.rb +3 -3
- data/tests/drivers/mock/images_test.rb +12 -0
- data/tests/drivers/mock/instances_test.rb +2 -0
- data/tests/rabbit_test.rb +2 -2
- data/views/addresses/_address.html.haml +6 -0
- data/views/addresses/associate.html.haml +12 -0
- data/views/addresses/index.html.haml +9 -0
- data/views/addresses/index.xml.haml +4 -0
- data/views/addresses/show.html.haml +21 -0
- data/views/addresses/show.xml.haml +14 -0
- data/views/api/show.html.haml +6 -11
- data/views/api/show.xml.haml +2 -0
- data/views/blobs/new.html.haml +24 -23
- data/views/blobs/show.html.haml +30 -31
- data/views/buckets/index.html.haml +9 -21
- data/views/buckets/index.xml.haml +3 -7
- data/views/buckets/new.html.haml +13 -12
- data/views/buckets/show.html.haml +22 -22
- data/views/buckets/show.xml.haml +5 -3
- data/views/docs/collection.html.haml +23 -34
- data/views/docs/collection.xml.haml +2 -2
- data/views/docs/index.html.haml +9 -13
- data/views/docs/index.xml.haml +1 -1
- data/views/docs/operation.html.haml +28 -38
- data/views/docs/operation.xml.haml +1 -1
- data/views/drivers/index.html.haml +8 -13
- data/views/drivers/show.html.haml +18 -18
- data/views/error.html.haml +32 -27
- data/views/errors/400.html.haml +41 -0
- data/views/errors/{validation_failure.xml.haml → 400.xml.haml} +0 -4
- data/views/errors/401.html.haml +41 -0
- data/views/errors/{auth_exception.xml.haml → 401.xml.haml} +0 -0
- data/views/errors/403.html.haml +42 -0
- data/views/errors/{not_allowed.xml.haml → 403.xml.haml} +0 -0
- data/views/errors/404.html.haml +29 -0
- data/views/errors/{not_found.xml.haml → 404.xml.haml} +1 -1
- data/views/errors/405.html.haml +29 -0
- data/views/errors/405.xml.haml +5 -0
- data/views/errors/500.html.haml +43 -0
- data/views/errors/500.xml.haml +5 -0
- data/views/errors/502.html.haml +43 -0
- data/views/errors/{backend_error.xml.haml → 502.xml.haml} +1 -2
- data/views/errors/backend_capability_failure.html.haml +27 -9
- data/views/firewalls/index.html.haml +15 -0
- data/views/firewalls/index.xml.haml +28 -0
- data/views/firewalls/new.html.haml +11 -0
- data/views/firewalls/new_rule.html.haml +20 -0
- data/views/firewalls/show.html.haml +42 -0
- data/views/firewalls/show.xml.haml +26 -0
- data/views/hardware_profiles/index.html.haml +15 -23
- data/views/hardware_profiles/show.html.haml +22 -18
- data/views/images/index.html.haml +11 -23
- data/views/images/index.xml.haml +4 -13
- data/views/images/new.html.haml +12 -13
- data/views/images/show.html.haml +26 -20
- data/views/images/show.xml.haml +2 -1
- data/views/instance_states/show.html.haml +21 -25
- data/views/instances/index.html.haml +13 -30
- data/views/instances/index.xml.haml +2 -23
- data/views/instances/new.html.haml +83 -88
- data/views/instances/show.html.haml +53 -55
- data/views/instances/show.xml.haml +12 -10
- data/views/keys/index.html.haml +13 -24
- data/views/keys/new.html.haml +7 -7
- data/views/keys/show.html.haml +26 -21
- data/views/layout.html.haml +28 -27
- data/views/load_balancers/index.html.haml +11 -31
- data/views/load_balancers/index.xml.haml +0 -1
- data/views/load_balancers/new.html.haml +1 -1
- data/views/load_balancers/show.html.haml +33 -34
- data/views/load_balancers/show.xml.haml +2 -2
- data/views/realms/index.html.haml +11 -24
- data/views/realms/index.xml.haml +2 -8
- data/views/realms/show.html.haml +17 -15
- data/views/realms/show.xml.haml +2 -1
- data/views/storage_snapshots/index.html.haml +11 -21
- data/views/storage_snapshots/index.xml.haml +2 -5
- data/views/storage_snapshots/new.html.haml +1 -1
- data/views/storage_snapshots/show.html.haml +21 -13
- data/views/storage_snapshots/show.xml.haml +2 -1
- data/views/storage_volumes/index.html.haml +11 -34
- data/views/storage_volumes/new.html.haml +1 -1
- data/views/storage_volumes/show.html.haml +33 -27
- data/views/storage_volumes/show.xml.haml +2 -1
- metadata +266 -178
- data/lib/sinatra/respond_to.rb +0 -248
- data/support/fedora/deltacloudd +0 -128
- data/support/fedora/rubygem-deltacloud-core.spec +0 -127
- data/views/accounts/index.html.haml +0 -11
- data/views/accounts/show.html.haml +0 -30
- data/views/errors/auth_exception.html.haml +0 -8
- data/views/errors/backend_error.html.haml +0 -22
- data/views/errors/not_allowed.html.haml +0 -6
- data/views/errors/not_found.html.haml +0 -6
- 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
|
data/lib/deltacloud/helpers.rb
CHANGED
@@ -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='#{
|
25
|
+
s = "<ul class='breadcrumb'><li class='first'><a href='#{root_url}'>δ</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
|
31
|
-
|
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
|
-
|
35
|
-
|
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
|
103
|
+
report_error(404)
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
-
def report_error(
|
108
|
-
@error = request.env['sinatra.error']
|
109
|
-
|
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 {
|
112
|
-
format.
|
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
|
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
|
-
"
|
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!(
|
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
|
-
|
186
|
+
api_url_for("#{@collection.name.to_s}")
|
184
187
|
elsif [:show, :stop, :start, :reboot, :attach, :detach].include?(@operation.name)
|
185
|
-
|
188
|
+
api_url_for("#{@collection.name.to_s}/:id/#{@operation.name}")
|
186
189
|
elsif [:destroy].include?(@operation.name)
|
187
|
-
|
190
|
+
api_url_for("#{@collection.name.to_s}/:id")
|
188
191
|
else
|
189
|
-
|
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
|
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
|
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 #
|
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(
|
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
|
76
|
-
end
|
75
|
+
end
|
76
|
+
end
|
77
77
|
#remove the original keys
|
78
78
|
self.delete_if{|k,v| remove.include?(k)}
|
79
|
-
end
|
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
|
-
|
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
|