vcloud-rest 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,113 @@
1
+ module VCloudClient
2
+ class Connection
3
+ ##
4
+ # Fetch details about a given catalog
5
+ def get_catalog(catalogId)
6
+ params = {
7
+ 'method' => :get,
8
+ 'command' => "/catalog/#{catalogId}"
9
+ }
10
+
11
+ response, headers = send_request(params)
12
+ description = response.css("Description").first
13
+ description = description.text unless description.nil?
14
+
15
+ items = {}
16
+ response.css("CatalogItem[type='application/vnd.vmware.vcloud.catalogItem+xml']").each do |item|
17
+ items[item['name']] = item['href'].gsub(/.*\/catalogItem\//, "")
18
+ end
19
+ { :id => catalogId, :description => description, :items => items }
20
+ end
21
+
22
+ ##
23
+ # Friendly helper method to fetch an catalog id by name
24
+ # - organization hash (from get_organization/get_organization_by_name)
25
+ # - catalog name
26
+ def get_catalog_id_by_name(organization, catalogName)
27
+ result = nil
28
+
29
+ organization[:catalogs].each do |catalog|
30
+ if catalog[0].downcase == catalogName.downcase
31
+ result = catalog[1]
32
+ end
33
+ end
34
+
35
+ result
36
+ end
37
+
38
+ ##
39
+ # Friendly helper method to fetch an catalog by name
40
+ # - organization hash (from get_organization/get_organization_by_name)
41
+ # - catalog name
42
+ def get_catalog_by_name(organization, catalogName)
43
+ result = nil
44
+
45
+ organization[:catalogs].each do |catalog|
46
+ if catalog[0].downcase == catalogName.downcase
47
+ result = get_catalog(catalog[1])
48
+ end
49
+ end
50
+
51
+ result
52
+ end
53
+
54
+ ##
55
+ # Fetch details about a given catalog item:
56
+ # - description
57
+ # - vApp templates
58
+ def get_catalog_item(catalogItemId)
59
+ params = {
60
+ 'method' => :get,
61
+ 'command' => "/catalogItem/#{catalogItemId}"
62
+ }
63
+
64
+ response, headers = send_request(params)
65
+ description = response.css("Description").first
66
+ description = description.text unless description.nil?
67
+
68
+ items = []
69
+ response.css("Entity[type='application/vnd.vmware.vcloud.vAppTemplate+xml']").each do |item|
70
+ itemId = item['href'].gsub(/.*\/vAppTemplate\/vappTemplate\-/, "")
71
+
72
+ # Fetch the catalogItemId information
73
+ params = {
74
+ 'method' => :get,
75
+ 'command' => "/vAppTemplate/vappTemplate-#{itemId}"
76
+ }
77
+ response, headers = send_request(params)
78
+
79
+ # VMs Hash for all the vApp VM entities
80
+ vms_hash = {}
81
+ response.css("/VAppTemplate/Children/Vm").each do |vmElem|
82
+ vmName = vmElem["name"]
83
+ vmId = vmElem["href"].gsub(/.*\/vAppTemplate\/vm\-/, "")
84
+
85
+ # Add the VM name/id to the VMs Hash
86
+ vms_hash[vmName] = { :id => vmId }
87
+ end
88
+
89
+ items << { :id => itemId,
90
+ :name => item['name'],
91
+ :vms_hash => vms_hash }
92
+ end
93
+ { :id => catalogItemId, :description => description, :items => items }
94
+ end
95
+
96
+ ##
97
+ # friendly helper method to fetch an catalogItem by name
98
+ # - catalogId (use get_catalog_name(org, name))
99
+ # - catalagItemName
100
+ def get_catalog_item_by_name(catalogId, catalogItemName)
101
+ result = nil
102
+ catalogElems = get_catalog(catalogId)
103
+
104
+ catalogElems[:items].each do |k, v|
105
+ if (k.downcase == catalogItemName.downcase)
106
+ result = get_catalog_item(v)
107
+ end
108
+ end
109
+
110
+ result
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,78 @@
1
+ module VCloudClient
2
+ class Connection
3
+ ##
4
+ # Fetch details about a given network
5
+ def get_network(networkId)
6
+ response = get_base_network(networkId)
7
+
8
+ name = response.css('OrgVdcNetwork').attribute('name').text
9
+
10
+ description = response.css("Description").first
11
+ description = description.text unless description.nil?
12
+
13
+ gateway = response.css('Gateway')
14
+ gateway = gateway.text unless gateway.nil?
15
+
16
+ netmask = response.css('Netmask')
17
+ netmask = netmask.text unless netmask.nil?
18
+
19
+ fence_mode = response.css('FenceMode')
20
+ fence_mode = fence_mode.text unless fence_mode.nil?
21
+
22
+ start_address = response.css('StartAddress')
23
+ start_address = start_address.text unless start_address.nil?
24
+
25
+ end_address = response.css('EndAddress')
26
+ end_address = end_address.text unless end_address.nil?
27
+
28
+
29
+ { :id => networkId, :name => name, :description => description,
30
+ :gateway => gateway, :netmask => netmask, :fence_mode => fence_mode,
31
+ :start_address => start_address, :end_address => end_address }
32
+ end
33
+
34
+ ##
35
+ # Friendly helper method to fetch an network id by name
36
+ # - organization hash (from get_organization/get_organization_by_name)
37
+ # - network name
38
+ def get_network_id_by_name(organization, networkName)
39
+ result = nil
40
+
41
+ organization[:networks].each do |network|
42
+ if network[0].downcase == networkName.downcase
43
+ result = network[1]
44
+ end
45
+ end
46
+
47
+ result
48
+ end
49
+
50
+ ##
51
+ # Friendly helper method to fetch an network by name
52
+ # - organization hash (from get_organization/get_organization_by_name)
53
+ # - network name
54
+ def get_network_by_name(organization, networkName)
55
+ result = nil
56
+
57
+ organization[:networks].each do |network|
58
+ if network[0].downcase == networkName.downcase
59
+ result = get_network(network[1])
60
+ end
61
+ end
62
+
63
+ result
64
+ end
65
+
66
+ private
67
+ # Get a network configuration
68
+ def get_base_network(networkId)
69
+ params = {
70
+ 'method' => :get,
71
+ 'command' => "/network/#{networkId}"
72
+ }
73
+
74
+ base_network, headers = send_request(params)
75
+ base_network
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,145 @@
1
+ module VCloudClient
2
+ class Connection
3
+ ##
4
+ # Fetch existing organizations and their IDs
5
+ def get_organizations
6
+ params = {
7
+ 'method' => :get,
8
+ 'command' => '/org'
9
+ }
10
+
11
+ response, headers = send_request(params)
12
+ orgs = response.css('OrgList Org')
13
+
14
+ results = {}
15
+ orgs.each do |org|
16
+ results[org['name']] = org['href'].gsub(/.*\/org\//, "")
17
+ end
18
+ results
19
+ end
20
+
21
+ ##
22
+ # friendly helper method to fetch an Organization Id by name
23
+ # - name (this isn't case sensitive)
24
+ def get_organization_id_by_name(name)
25
+ result = nil
26
+
27
+ # Fetch all organizations
28
+ organizations = get_organizations()
29
+
30
+ organizations.each do |organization|
31
+ if organization[0].downcase == name.downcase
32
+ result = organization[1]
33
+ end
34
+ end
35
+ result
36
+ end
37
+
38
+
39
+ ##
40
+ # friendly helper method to fetch an Organization by name
41
+ # - name (this isn't case sensitive)
42
+ def get_organization_by_name(name)
43
+ result = nil
44
+
45
+ # Fetch all organizations
46
+ organizations = get_organizations()
47
+
48
+ organizations.each do |organization|
49
+ if organization[0].downcase == name.downcase
50
+ result = get_organization(organization[1])
51
+ end
52
+ end
53
+ result
54
+ end
55
+
56
+ ##
57
+ # Fetch details about an organization:
58
+ # - catalogs
59
+ # - vdcs
60
+ # - networks
61
+ # - task lists
62
+ def get_organization(orgId)
63
+ params = {
64
+ 'method' => :get,
65
+ 'command' => "/org/#{orgId}"
66
+ }
67
+
68
+ response, headers = send_request(params)
69
+ catalogs = {}
70
+ response.css("Link[type='application/vnd.vmware.vcloud.catalog+xml']").each do |item|
71
+ catalogs[item['name']] = item['href'].gsub(/.*\/catalog\//, "")
72
+ end
73
+
74
+ vdcs = {}
75
+ response.css("Link[type='application/vnd.vmware.vcloud.vdc+xml']").each do |item|
76
+ vdcs[item['name']] = item['href'].gsub(/.*\/vdc\//, "")
77
+ end
78
+
79
+ networks = {}
80
+ response.css("Link[type='application/vnd.vmware.vcloud.orgNetwork+xml']").each do |item|
81
+ networks[item['name']] = item['href'].gsub(/.*\/network\//, "")
82
+ end
83
+
84
+ tasklists = {}
85
+ response.css("Link[type='application/vnd.vmware.vcloud.tasksList+xml']").each do |item|
86
+ tasklists[item['name']] = item['href'].gsub(/.*\/tasksList\//, "")
87
+ end
88
+
89
+ { :catalogs => catalogs, :vdcs => vdcs, :networks => networks, :tasklists => tasklists }
90
+ end
91
+
92
+ ##
93
+ # Fetch tasks from a given task list
94
+ #
95
+ # Note: id can be retrieved using get_organization
96
+ def get_tasks_list(id)
97
+ params = {
98
+ 'method' => :get,
99
+ 'command' => "/tasksList/#{id}"
100
+ }
101
+
102
+ response, headers = send_request(params)
103
+
104
+ tasks = []
105
+
106
+ response.css('Task').each do |task|
107
+ id = task['href'].gsub(/.*\/task\//, "")
108
+ operation = task['operationName']
109
+ status = task['status']
110
+ error = nil
111
+ error = task.css('Error').first['message'] if task['status'] == 'error'
112
+ start_time = task['startTime']
113
+ end_time = task['endTime']
114
+ user_canceled = task['cancelRequested'] == 'true'
115
+
116
+ tasks << {
117
+ :id => id,
118
+ :operation => operation,
119
+ :status => status,
120
+ :error => error,
121
+ :start_time => start_time,
122
+ :end_time => end_time,
123
+ :user_canceled => user_canceled
124
+ }
125
+ end
126
+ tasks
127
+ end
128
+
129
+ ##
130
+ # Cancel a given task
131
+ #
132
+ # The task will be marked for cancellation
133
+ def cancel_task(id)
134
+ params = {
135
+ 'method' => :post,
136
+ 'command' => "/task/#{id}/action/cancel"
137
+ }
138
+
139
+ # Nothing useful is returned here
140
+ # If return code is 20x return true
141
+ send_request(params)
142
+ true
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,251 @@
1
+ module VCloudClient
2
+ class Connection
3
+ ##
4
+ # Upload an OVF package
5
+ # - vdcId
6
+ # - vappName
7
+ # - vappDescription
8
+ # - ovfFile
9
+ # - catalogId
10
+ # - uploadOptions {}
11
+ def upload_ovf(vdcId, vappName, vappDescription, ovfFile, catalogId, uploadOptions={})
12
+ raise ::IOError, "OVF #{ovfFile} is missing." unless File.exists?(ovfFile)
13
+
14
+ # if send_manifest is not set, setting it true
15
+ if uploadOptions[:send_manifest].nil? || uploadOptions[:send_manifest]
16
+ uploadManifest = "true"
17
+ else
18
+ uploadManifest = "false"
19
+ end
20
+
21
+ builder = Nokogiri::XML::Builder.new do |xml|
22
+ xml.UploadVAppTemplateParams(
23
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
24
+ "xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
25
+ "manifestRequired" => uploadManifest,
26
+ "name" => vappName) {
27
+ xml.Description vappDescription
28
+ }
29
+ end
30
+
31
+ params = {
32
+ 'method' => :post,
33
+ 'command' => "/vdc/#{vdcId}/action/uploadVAppTemplate"
34
+ }
35
+
36
+ response, headers = send_request(
37
+ params,
38
+ builder.to_xml,
39
+ "application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml"
40
+ )
41
+
42
+ # Get vAppTemplate Link from location
43
+ vAppTemplate = headers[:location].gsub(/.*\/vAppTemplate\/vappTemplate\-/, "")
44
+ descriptorUpload = response.css("Files Link [rel='upload:default']").first[:href].gsub("#{@host_url}/transfer/", "")
45
+ transferGUID = descriptorUpload.gsub("/descriptor.ovf", "")
46
+
47
+ ovfFileBasename = File.basename(ovfFile, ".ovf")
48
+ ovfDir = File.dirname(ovfFile)
49
+
50
+ # Send OVF Descriptor
51
+ uploadURL = "/transfer/#{descriptorUpload}"
52
+ uploadFile = "#{ovfDir}/#{ovfFileBasename}.ovf"
53
+ upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
54
+
55
+ @logger.debug "OVF Descriptor uploaded."
56
+
57
+ # Begin the catch for upload interruption
58
+ begin
59
+ params = {
60
+ 'method' => :get,
61
+ 'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
62
+ }
63
+
64
+ # Loop to wait for the upload links to show up in the vAppTemplate we just created
65
+ while true
66
+ response, headers = send_request(params)
67
+
68
+ errored_task = response.css("Tasks Task [status='error']").first
69
+ if errored_task
70
+ error_msg = errored_task.css('Error').first['message']
71
+ raise OVFError, "OVF Upload failed: #{error_msg}"
72
+ end
73
+
74
+ break unless response.css("Files Link [rel='upload:default']").count == 1
75
+ sleep 1
76
+ end
77
+
78
+ if uploadManifest == "true"
79
+ uploadURL = "/transfer/#{transferGUID}/descriptor.mf"
80
+ uploadFile = "#{ovfDir}/#{ovfFileBasename}.mf"
81
+ upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
82
+ @logger.debug "OVF Manifest uploaded."
83
+ end
84
+
85
+ # Start uploading OVF VMDK files
86
+ params = {
87
+ 'method' => :get,
88
+ 'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
89
+ }
90
+ response, headers = send_request(params)
91
+ response.css("Files File [bytesTransferred='0'] Link [rel='upload:default']").each do |file|
92
+ fileName = file[:href].gsub("#{@host_url}/transfer/#{transferGUID}/","")
93
+ uploadFile = "#{ovfDir}/#{fileName}"
94
+ uploadURL = "/transfer/#{transferGUID}/#{fileName}"
95
+ upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
96
+ end
97
+
98
+ # Add item to the catalog catalogId
99
+ builder = Nokogiri::XML::Builder.new do |xml|
100
+ xml.CatalogItem(
101
+ "xmlns" => "http://www.vmware.com/vcloud/v1.5",
102
+ "type" => "application/vnd.vmware.vcloud.catalogItem+xml",
103
+ "name" => vappName) {
104
+ xml.Description vappDescription
105
+ xml.Entity(
106
+ "href" => "#{@api_url}/vAppTemplate/vappTemplate-#{vAppTemplate}"
107
+ )
108
+ }
109
+ end
110
+
111
+ params = {
112
+ 'method' => :post,
113
+ 'command' => "/catalog/#{catalogId}/catalogItems"
114
+ }
115
+
116
+ @logger.debug "Add item to catalog."
117
+ response, headers = send_request(params, builder.to_xml,
118
+ "application/vnd.vmware.vcloud.catalogItem+xml")
119
+
120
+ entity = response.css("Entity").first
121
+
122
+ # TODO: the best thing would detect the real importing status.
123
+ result = {}
124
+ if entity
125
+ result[:id] = entity['href'].gsub(/.*\/vAppTemplate\/vappTemplate\-/, "")
126
+ result[:name] = entity['name']
127
+ end
128
+ result
129
+ rescue Exception => e
130
+ @logger.error "Exception detected: #{e.message}."
131
+
132
+ # Get vAppTemplate Task
133
+ params = {
134
+ 'method' => :get,
135
+ 'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
136
+ }
137
+ response, headers = send_request(params)
138
+
139
+ # Cancel Task
140
+ # Note that it might not exist (i.e., error for existing vdc entity)
141
+ tasks = response.css("Tasks")
142
+ unless tasks.empty?
143
+ tasks.css("Task").each do |task|
144
+ if task['status'] == 'error'
145
+ @logger.error task.css('Error').first['message']
146
+ else
147
+ id = task['href'].gsub(/.*\/task\//, "")
148
+ @logger.error "Aborting task #{id}..."
149
+ cancel_task(id)
150
+ end
151
+ end
152
+ end
153
+
154
+ raise e
155
+ end
156
+ end
157
+
158
+ private
159
+ ##
160
+ # Upload a large file in configurable chunks, output an optional progressbar
161
+ def upload_file(uploadURL, uploadFile, vAppTemplate, config={})
162
+ raise ::IOError, "#{uploadFile} not found." unless File.exists?(uploadFile)
163
+
164
+ # Set chunksize to 10M if not specified otherwise
165
+ chunkSize = (config[:chunksize] || 10485760)
166
+
167
+ # Set progress bar to default format if not specified otherwise
168
+ progressBarFormat = (config[:progressbar_format] || "%e <%B> %p%% %t")
169
+
170
+ # Set progress bar length to 120 if not specified otherwise
171
+ progressBarLength = (config[:progressbar_length] || 120)
172
+
173
+ # Open our file for upload
174
+ uploadFileHandle = File.new(uploadFile, "rb" )
175
+ fileName = File.basename(uploadFileHandle)
176
+
177
+ progressBarTitle = "Uploading: " + uploadFile.to_s
178
+
179
+ # Create a progressbar object if progress bar is enabled
180
+ if config[:progressbar_enable] == true && uploadFileHandle.size.to_i > chunkSize
181
+ progressbar = ProgressBar.create(
182
+ :title => progressBarTitle,
183
+ :starting_at => 0,
184
+ :total => uploadFileHandle.size.to_i,
185
+ :length => progressBarLength,
186
+ :format => progressBarFormat
187
+ )
188
+ else
189
+ @logger.info progressBarTitle
190
+ end
191
+ # Create a new HTTP client
192
+ clnt = HTTPClient.new
193
+
194
+ # Disable SSL cert verification
195
+ clnt.ssl_config.verify_mode=(OpenSSL::SSL::VERIFY_NONE)
196
+
197
+ # Suppress SSL depth message
198
+ clnt.ssl_config.verify_callback=proc{ |ok, ctx|; true };
199
+
200
+ # Perform ranged upload until the file reaches its end
201
+ until uploadFileHandle.eof?
202
+
203
+ # Create ranges for this chunk upload
204
+ rangeStart = uploadFileHandle.pos
205
+ rangeStop = uploadFileHandle.pos.to_i + chunkSize
206
+
207
+ # Read current chunk
208
+ fileContent = uploadFileHandle.read(chunkSize)
209
+
210
+ # If statement to handle last chunk transfer if is > than filesize
211
+ if rangeStop.to_i > uploadFileHandle.size.to_i
212
+ contentRange = "bytes #{rangeStart.to_s}-#{uploadFileHandle.size.to_s}/#{uploadFileHandle.size.to_s}"
213
+ rangeLen = uploadFileHandle.size.to_i - rangeStart.to_i
214
+ else
215
+ contentRange = "bytes #{rangeStart.to_s}-#{rangeStop.to_s}/#{uploadFileHandle.size.to_s}"
216
+ rangeLen = rangeStop.to_i - rangeStart.to_i
217
+ end
218
+
219
+ # Build headers
220
+ extheader = {
221
+ 'x-vcloud-authorization' => @auth_key,
222
+ 'Content-Range' => contentRange,
223
+ 'Content-Length' => rangeLen.to_s
224
+ }
225
+
226
+ begin
227
+ uploadRequest = "#{@host_url}#{uploadURL}"
228
+ connection = clnt.request('PUT', uploadRequest, nil, fileContent, extheader)
229
+
230
+ if config[:progressbar_enable] == true && uploadFileHandle.size.to_i > chunkSize
231
+ params = {
232
+ 'method' => :get,
233
+ 'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
234
+ }
235
+ response, headers = send_request(params)
236
+
237
+ response.css("Files File [name='#{fileName}']").each do |file|
238
+ progressbar.progress=file[:bytesTransferred].to_i
239
+ end
240
+ end
241
+ rescue
242
+ retryTime = (config[:retry_time] || 5)
243
+ @logger.warn "Range #{contentRange} failed to upload, retrying the chunk in #{retryTime.to_s} seconds, to stop the action press CTRL+C."
244
+ sleep retryTime.to_i
245
+ retry
246
+ end
247
+ end
248
+ uploadFileHandle.close
249
+ end
250
+ end
251
+ end