vagrant-vcloudair 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.rubocop.yml +34 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +109 -0
- data/lib/vagrant-vcloudair.rb +63 -0
- data/lib/vagrant-vcloudair/action.rb +298 -0
- data/lib/vagrant-vcloudair/action/announce_ssh_exec.rb +22 -0
- data/lib/vagrant-vcloudair/action/build_vapp.rb +235 -0
- data/lib/vagrant-vcloudair/action/connect_vcloud.rb +54 -0
- data/lib/vagrant-vcloudair/action/destroy_vapp.rb +54 -0
- data/lib/vagrant-vcloudair/action/destroy_vm.rb +37 -0
- data/lib/vagrant-vcloudair/action/disconnect_vcloud.rb +31 -0
- data/lib/vagrant-vcloudair/action/forward_ports.rb +132 -0
- data/lib/vagrant-vcloudair/action/handle_nat_port_collisions.rb +153 -0
- data/lib/vagrant-vcloudair/action/inventory_check.rb +210 -0
- data/lib/vagrant-vcloudair/action/is_bridged.rb +29 -0
- data/lib/vagrant-vcloudair/action/is_created.rb +35 -0
- data/lib/vagrant-vcloudair/action/is_last_vm.rb +31 -0
- data/lib/vagrant-vcloudair/action/is_paused.rb +20 -0
- data/lib/vagrant-vcloudair/action/is_running.rb +20 -0
- data/lib/vagrant-vcloudair/action/message_already_running.rb +16 -0
- data/lib/vagrant-vcloudair/action/message_cannot_suspend.rb +16 -0
- data/lib/vagrant-vcloudair/action/message_not_created.rb +16 -0
- data/lib/vagrant-vcloudair/action/message_not_running.rb +16 -0
- data/lib/vagrant-vcloudair/action/message_will_not_destroy.rb +21 -0
- data/lib/vagrant-vcloudair/action/power_off.rb +33 -0
- data/lib/vagrant-vcloudair/action/power_off_vapp.rb +40 -0
- data/lib/vagrant-vcloudair/action/power_on.rb +39 -0
- data/lib/vagrant-vcloudair/action/read_ssh_info.rb +153 -0
- data/lib/vagrant-vcloudair/action/read_state.rb +51 -0
- data/lib/vagrant-vcloudair/action/resume.rb +25 -0
- data/lib/vagrant-vcloudair/action/suspend.rb +25 -0
- data/lib/vagrant-vcloudair/action/unmap_port_forwardings.rb +74 -0
- data/lib/vagrant-vcloudair/cap/forwarded_ports.rb +38 -0
- data/lib/vagrant-vcloudair/cap/public_address.rb +18 -0
- data/lib/vagrant-vcloudair/cap/rdp_info.rb +18 -0
- data/lib/vagrant-vcloudair/cap/winrm_info.rb +15 -0
- data/lib/vagrant-vcloudair/command.rb +285 -0
- data/lib/vagrant-vcloudair/config.rb +205 -0
- data/lib/vagrant-vcloudair/driver/base.rb +643 -0
- data/lib/vagrant-vcloudair/driver/meta.rb +202 -0
- data/lib/vagrant-vcloudair/driver/version_5_1.rb +2019 -0
- data/lib/vagrant-vcloudair/errors.rb +77 -0
- data/lib/vagrant-vcloudair/model/forwarded_port.rb +66 -0
- data/lib/vagrant-vcloudair/plugin.rb +111 -0
- data/lib/vagrant-vcloudair/provider.rb +41 -0
- data/lib/vagrant-vcloudair/util/compile_forwarded_ports.rb +34 -0
- data/lib/vagrant-vcloudair/version.rb +5 -0
- data/locales/en.yml +169 -0
- data/vagrant-vcloudair.gemspec +33 -0
- metadata +266 -0
@@ -0,0 +1,202 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2012 Stefano Tortarolo
|
3
|
+
# Copyright 2013 Fabio Rapposelli and Timo Sugliani
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# 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,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'forwardable'
|
19
|
+
require 'log4r'
|
20
|
+
require 'nokogiri'
|
21
|
+
require 'httpclient'
|
22
|
+
|
23
|
+
require File.expand_path('../base', __FILE__)
|
24
|
+
|
25
|
+
module VagrantPlugins
|
26
|
+
module VCloudAir
|
27
|
+
module Driver
|
28
|
+
class Meta < Base
|
29
|
+
# We use forwardable to do all our driver forwarding
|
30
|
+
extend Forwardable
|
31
|
+
attr_reader :driver
|
32
|
+
|
33
|
+
def initialize(cloud_id, username, password, vdc_name)
|
34
|
+
# Setup the base
|
35
|
+
super()
|
36
|
+
|
37
|
+
@username = username
|
38
|
+
@password = password
|
39
|
+
|
40
|
+
@logger = Log4r::Logger.new('vagrant::provider::vcloudair::meta')
|
41
|
+
|
42
|
+
# Logging into vCloud Air
|
43
|
+
params = {
|
44
|
+
'method' => :post,
|
45
|
+
'command' => '/vchs/sessions'
|
46
|
+
}
|
47
|
+
|
48
|
+
_response, headers = send_vcloudair_request(params)
|
49
|
+
|
50
|
+
unless headers.key?('x-vchs-authorization')
|
51
|
+
fail Errors::InvalidRequestError,
|
52
|
+
:message => 'Failed to authenticate: ' \
|
53
|
+
'missing x-vchs-authorization header'
|
54
|
+
end
|
55
|
+
|
56
|
+
@vcloudair_auth_key = headers['x-vchs-authorization']
|
57
|
+
|
58
|
+
# Get Services available
|
59
|
+
params = {
|
60
|
+
'method' => :get,
|
61
|
+
'command' => '/vchs/services'
|
62
|
+
}
|
63
|
+
|
64
|
+
response, _headers = send_vcloudair_request(params)
|
65
|
+
services = response.css('Services Service')
|
66
|
+
|
67
|
+
service_id = cloud_id || vdc_name
|
68
|
+
services.each do |service|
|
69
|
+
if service['serviceId'] == service_id
|
70
|
+
@compute_id = URI(service['href']).path.gsub('/api', '')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
fail Errors::ServiceNotFound if @compute_id.nil?
|
75
|
+
|
76
|
+
# Get Service Link to vCloud Director
|
77
|
+
params = {
|
78
|
+
'method' => :get,
|
79
|
+
'command' => @compute_id
|
80
|
+
}
|
81
|
+
|
82
|
+
response, _headers = send_vcloudair_request(params)
|
83
|
+
|
84
|
+
vdcs = response.css('Compute VdcRef')
|
85
|
+
|
86
|
+
vdcs.each do |vdc|
|
87
|
+
if vdc['name'] == vdc_name
|
88
|
+
@vdc_id = URI(vdc['href']).path.gsub('/api', '')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
fail Errors::VdcNotFound, :message => vdc_name if @vdc_id.nil?
|
93
|
+
|
94
|
+
# Authenticate to vCloud Director
|
95
|
+
params = {
|
96
|
+
'method' => :post,
|
97
|
+
'command' => "#{@vdc_id}/vcloudsession"
|
98
|
+
}
|
99
|
+
|
100
|
+
response, _headers = send_vcloudair_request(params)
|
101
|
+
|
102
|
+
vdclinks = response.css('VCloudSession VdcLink')
|
103
|
+
|
104
|
+
vdclinks.each do |vdclink|
|
105
|
+
if vdclink['name'] == service_id
|
106
|
+
uri = URI(vdclink['href'])
|
107
|
+
@api_url = "#{uri.scheme}://#{uri.host}:#{uri.port}/api"
|
108
|
+
@host_url = "#{uri.scheme}://#{uri.host}:#{uri.port}"
|
109
|
+
@auth_key = vdclink['authorizationToken']
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
fail Errors::ObjectNotFound,
|
114
|
+
:message => 'Cannot find link to backend \
|
115
|
+
vCloud Director Instance' if @api_url.nil?
|
116
|
+
|
117
|
+
@org_name = vdc_name
|
118
|
+
|
119
|
+
# Read and assign the version of vCloud Air we know which
|
120
|
+
# specific driver to instantiate.
|
121
|
+
@logger.debug("Asking API Version with host_url: #{@host_url}")
|
122
|
+
@version = get_api_version(@host_url) || ''
|
123
|
+
|
124
|
+
# Instantiate the proper version driver for vCloud Air
|
125
|
+
@logger.debug("Finding driver for vCloud Air version: #{@version}")
|
126
|
+
driver_map = {
|
127
|
+
'5.1' => Version_5_1,
|
128
|
+
'5.5' => Version_5_1,
|
129
|
+
'5.6' => Version_5_1,
|
130
|
+
'5.7' => Version_5_1
|
131
|
+
}
|
132
|
+
|
133
|
+
if @version.start_with?('0.9') ||
|
134
|
+
@version.start_with?('1.0') ||
|
135
|
+
@version.start_with?('1.5')
|
136
|
+
# We only support vCloud Air 5.1 or higher.
|
137
|
+
fail Errors::VCloudAirOldVersion, :version => @version
|
138
|
+
end
|
139
|
+
|
140
|
+
driver_klass = nil
|
141
|
+
driver_map.each do |key, klass|
|
142
|
+
if @version.start_with?(key)
|
143
|
+
driver_klass = klass
|
144
|
+
break
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
unless driver_klass
|
149
|
+
supported_versions = driver_map.keys.sort.join(', ')
|
150
|
+
fail Errors::VCloudAirInvalidVersion,
|
151
|
+
:supported_versions => supported_versions
|
152
|
+
end
|
153
|
+
|
154
|
+
@logger.info("Using vCloud Air driver: #{driver_klass}")
|
155
|
+
@driver = driver_klass.new(@api_url, @host_url, @auth_key, @org_name)
|
156
|
+
end
|
157
|
+
|
158
|
+
def_delegators :@driver,
|
159
|
+
:login,
|
160
|
+
:logout,
|
161
|
+
:get_organizations,
|
162
|
+
:get_organization_id_by_name,
|
163
|
+
:get_organization_by_name,
|
164
|
+
:get_organization,
|
165
|
+
:get_catalog,
|
166
|
+
:get_catalog_id_by_name,
|
167
|
+
:get_catalog_by_name,
|
168
|
+
:get_vdc,
|
169
|
+
:get_vdc_id_by_name,
|
170
|
+
:get_vdc_by_name,
|
171
|
+
:get_catalog_item,
|
172
|
+
:get_catalog_item_by_name,
|
173
|
+
:get_vapp,
|
174
|
+
:delete_vapp,
|
175
|
+
:poweroff_vapp,
|
176
|
+
:suspend_vapp,
|
177
|
+
:reboot_vapp,
|
178
|
+
:reset_vapp,
|
179
|
+
:poweron_vapp,
|
180
|
+
:create_vapp_from_template,
|
181
|
+
:compose_vapp_from_vm,
|
182
|
+
:get_vapp_template,
|
183
|
+
:set_vapp_port_forwarding_rules,
|
184
|
+
:get_vapp_port_forwarding_rules,
|
185
|
+
:get_vapp_edge_public_ip,
|
186
|
+
:upload_ovf,
|
187
|
+
:get_task,
|
188
|
+
:wait_task_completion,
|
189
|
+
:set_vapp_network_config,
|
190
|
+
:set_vm_network_config,
|
191
|
+
:set_vm_guest_customization,
|
192
|
+
:get_vm,
|
193
|
+
:send_request,
|
194
|
+
:upload_file,
|
195
|
+
:convert_vapp_status
|
196
|
+
|
197
|
+
protected
|
198
|
+
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,2019 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2012 Stefano Tortarolo
|
3
|
+
# Copyright 2013 Fabio Rapposelli and Timo Sugliani
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# 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,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'ruby-progressbar'
|
19
|
+
require 'set'
|
20
|
+
require 'netaddr'
|
21
|
+
require 'uri'
|
22
|
+
|
23
|
+
module VagrantPlugins
|
24
|
+
module VCloudAir
|
25
|
+
module Driver
|
26
|
+
# Main class to access vCloud Air rest APIs
|
27
|
+
class Version_5_1 < Base
|
28
|
+
attr_reader :auth_key, :id
|
29
|
+
|
30
|
+
##
|
31
|
+
# Init the driver with the Vagrantfile information
|
32
|
+
def initialize(api_url, host_url, auth_key, org_name)
|
33
|
+
@logger = Log4r::Logger.new('vagrant::provider::vcloudair::driver_5_1')
|
34
|
+
@api_url = api_url
|
35
|
+
@host_url = host_url
|
36
|
+
@auth_key = auth_key
|
37
|
+
@org_name = org_name
|
38
|
+
@api_version = '5.1'
|
39
|
+
@id = nil
|
40
|
+
|
41
|
+
@cached_vapp_edge_public_ips = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Authenticate against the specified server
|
46
|
+
def login
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Destroy the current session
|
51
|
+
def logout
|
52
|
+
params = {
|
53
|
+
'method' => :delete,
|
54
|
+
'command' => '/session'
|
55
|
+
}
|
56
|
+
|
57
|
+
_response, _headers = send_request(params)
|
58
|
+
# reset auth key to nil
|
59
|
+
@auth_key = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Fetch existing organizations and their IDs
|
64
|
+
def get_organizations
|
65
|
+
params = {
|
66
|
+
'method' => :get,
|
67
|
+
'command' => '/org'
|
68
|
+
}
|
69
|
+
|
70
|
+
response, _headers = send_request(params)
|
71
|
+
orgs = response.css('OrgList Org')
|
72
|
+
|
73
|
+
results = {}
|
74
|
+
orgs.each do |org|
|
75
|
+
results[org['name']] = URI(org['href']).path.gsub('/api/org/', '')
|
76
|
+
end
|
77
|
+
results
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# friendly helper method to fetch an Organization Id by name
|
82
|
+
# - name (this isn't case sensitive)
|
83
|
+
def get_organization_id_by_name(name)
|
84
|
+
result = nil
|
85
|
+
|
86
|
+
# Fetch all organizations
|
87
|
+
organizations = get_organizations
|
88
|
+
|
89
|
+
organizations.each do |organization|
|
90
|
+
if organization[0].downcase == name.downcase
|
91
|
+
result = organization[1]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# friendly helper method to fetch an Organization by name
|
99
|
+
# - name (this isn't case sensitive)
|
100
|
+
def get_organization_by_name(name)
|
101
|
+
result = nil
|
102
|
+
|
103
|
+
# Fetch all organizations
|
104
|
+
organizations = get_organizations
|
105
|
+
|
106
|
+
organizations.each do |organization|
|
107
|
+
if organization[0].downcase == name.downcase
|
108
|
+
result = get_organization(organization[1])
|
109
|
+
end
|
110
|
+
end
|
111
|
+
result
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Fetch details about an organization:
|
116
|
+
# - catalogs
|
117
|
+
# - vdcs
|
118
|
+
# - networks
|
119
|
+
def get_organization(org_id)
|
120
|
+
params = {
|
121
|
+
'method' => :get,
|
122
|
+
'command' => "/org/#{org_id}"
|
123
|
+
}
|
124
|
+
|
125
|
+
response, _headers = send_request(params)
|
126
|
+
|
127
|
+
catalogs = {}
|
128
|
+
response.css(
|
129
|
+
"Link[type='application/vnd.vmware.vcloud.catalog+xml']"
|
130
|
+
).each do |item|
|
131
|
+
catalogs[item['name']] = URI(item['href']).path.gsub(
|
132
|
+
'/api/catalog/', ''
|
133
|
+
)
|
134
|
+
end
|
135
|
+
|
136
|
+
vdcs = {}
|
137
|
+
response.css(
|
138
|
+
"Link[type='application/vnd.vmware.vcloud.vdc+xml']"
|
139
|
+
).each do |item|
|
140
|
+
vdcs[item['name']] = URI(item['href']).path.gsub(
|
141
|
+
'/api/vdc/', ''
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
networks = {}
|
146
|
+
response.css(
|
147
|
+
"Link[type='application/vnd.vmware.vcloud.orgNetwork+xml']"
|
148
|
+
).each do |item|
|
149
|
+
networks[item['name']] = URI(item['href']).path.gsub(
|
150
|
+
'/api/network/', ''
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
154
|
+
tasklists = {}
|
155
|
+
response.css(
|
156
|
+
"Link[type='application/vnd.vmware.vcloud.tasksList+xml']"
|
157
|
+
).each do |item|
|
158
|
+
tasklists[item['name']] = URI(item['href']).path.gsub(
|
159
|
+
'/api/tasksList/', ''
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
{
|
164
|
+
:catalogs => catalogs,
|
165
|
+
:vdcs => vdcs,
|
166
|
+
:networks => networks,
|
167
|
+
:tasklists => tasklists
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# Fetch details about a given catalog
|
173
|
+
def get_catalog(catalog_id)
|
174
|
+
params = {
|
175
|
+
'method' => :get,
|
176
|
+
'command' => "/catalog/#{catalog_id}"
|
177
|
+
}
|
178
|
+
|
179
|
+
response, _headers = send_request(params)
|
180
|
+
description = response.css('Description').first
|
181
|
+
description = description.text unless description.nil?
|
182
|
+
|
183
|
+
items = {}
|
184
|
+
response.css(
|
185
|
+
"CatalogItem[type='application/vnd.vmware.vcloud.catalogItem+xml']"
|
186
|
+
).each do |item|
|
187
|
+
items[item['name']] = URI(item['href']).path.gsub(
|
188
|
+
'/api/catalogItem/', ''
|
189
|
+
)
|
190
|
+
end
|
191
|
+
{ :description => description, :items => items }
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# Friendly helper method to fetch an catalog id by name
|
196
|
+
# - organization hash (from get_organization/get_organization_by_name)
|
197
|
+
# - catalog name
|
198
|
+
def get_catalog_id_by_name(organization, catalog_name)
|
199
|
+
result = nil
|
200
|
+
|
201
|
+
organization[:catalogs].each do |catalog|
|
202
|
+
if catalog[0].downcase == catalog_name.downcase
|
203
|
+
result = catalog[1]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
if result.nil?
|
208
|
+
# catalog not found, search in global catalogs as well
|
209
|
+
# that are not listed in organization directly
|
210
|
+
params = {
|
211
|
+
'method' => :get,
|
212
|
+
'command' => '/catalogs/query/',
|
213
|
+
'cacheable' => true
|
214
|
+
}
|
215
|
+
|
216
|
+
response, _headers = send_request(params)
|
217
|
+
|
218
|
+
catalogs = {}
|
219
|
+
response.css(
|
220
|
+
'CatalogRecord'
|
221
|
+
).each do |item|
|
222
|
+
catalogs[item['name']] = URI(item['href']).path.gsub(
|
223
|
+
'/api/catalog/', ''
|
224
|
+
)
|
225
|
+
end
|
226
|
+
|
227
|
+
catalogs.each do |catalog|
|
228
|
+
if catalog[0].downcase == catalog_name.downcase
|
229
|
+
result = catalog[1]
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
result
|
236
|
+
end
|
237
|
+
|
238
|
+
##
|
239
|
+
# Friendly helper method to fetch an catalog by name
|
240
|
+
# - organization hash (from get_organization/get_organization_by_name)
|
241
|
+
# - catalog name
|
242
|
+
def get_catalog_by_name(organization, catalog_name)
|
243
|
+
result = nil
|
244
|
+
|
245
|
+
organization[:catalogs].each do |catalog|
|
246
|
+
if catalog[0].downcase == catalog_name.downcase
|
247
|
+
result = get_catalog(catalog[1])
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
result
|
252
|
+
end
|
253
|
+
|
254
|
+
##
|
255
|
+
# Fetch details about a given vdc:
|
256
|
+
# - description
|
257
|
+
# - vapps
|
258
|
+
# - networks
|
259
|
+
def get_vdc(vdc_id)
|
260
|
+
params = {
|
261
|
+
'method' => :get,
|
262
|
+
'command' => "/vdc/#{vdc_id}"
|
263
|
+
}
|
264
|
+
|
265
|
+
response, _headers = send_request(params)
|
266
|
+
description = response.css('Description').first
|
267
|
+
description = description.text unless description.nil?
|
268
|
+
|
269
|
+
vapps = {}
|
270
|
+
response.css(
|
271
|
+
"ResourceEntity[type='application/vnd.vmware.vcloud.vApp+xml']"
|
272
|
+
).each do |item|
|
273
|
+
vapps[item['name']] = URI(item['href']).path.gsub(
|
274
|
+
'/api/vApp/vapp-', ''
|
275
|
+
)
|
276
|
+
end
|
277
|
+
|
278
|
+
networks = {}
|
279
|
+
response.css(
|
280
|
+
"Network[type='application/vnd.vmware.vcloud.network+xml']"
|
281
|
+
).each do |item|
|
282
|
+
networks[item['name']] = URI(item['href']).path.gsub(
|
283
|
+
'/api/network/', ''
|
284
|
+
)
|
285
|
+
end
|
286
|
+
{
|
287
|
+
:description => description, :vapps => vapps, :networks => networks
|
288
|
+
}
|
289
|
+
end
|
290
|
+
|
291
|
+
##
|
292
|
+
# Friendly helper method to fetch a Organization VDC Id by name
|
293
|
+
# - Organization object
|
294
|
+
# - Organization VDC Name
|
295
|
+
def get_vdc_id_by_name(organization, vdc_name)
|
296
|
+
result = nil
|
297
|
+
|
298
|
+
organization[:vdcs].each do |vdc|
|
299
|
+
if vdc[0].downcase == vdc_name.downcase
|
300
|
+
result = vdc[1]
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
result
|
305
|
+
end
|
306
|
+
|
307
|
+
##
|
308
|
+
# Friendly helper method to fetch a Organization VDC by name
|
309
|
+
# - Organization object
|
310
|
+
# - Organization VDC Name
|
311
|
+
def get_vdc_by_name(organization, vdc_name)
|
312
|
+
result = nil
|
313
|
+
|
314
|
+
organization[:vdcs].each do |vdc|
|
315
|
+
if vdc[0].downcase == vdc_name.downcase
|
316
|
+
result = get_vdc(vdc[1])
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
result
|
321
|
+
end
|
322
|
+
|
323
|
+
##
|
324
|
+
# Fetch details about a given catalog item:
|
325
|
+
# - description
|
326
|
+
# - vApp templates
|
327
|
+
def get_catalog_item(catalog_item_id)
|
328
|
+
params = {
|
329
|
+
'method' => :get,
|
330
|
+
'command' => "/catalogItem/#{catalog_item_id}"
|
331
|
+
}
|
332
|
+
|
333
|
+
response, _headers = send_request(params)
|
334
|
+
description = response.css('Description').first
|
335
|
+
description = description.text unless description.nil?
|
336
|
+
|
337
|
+
items = {}
|
338
|
+
response.css(
|
339
|
+
"Entity[type='application/vnd.vmware.vcloud.vAppTemplate+xml']"
|
340
|
+
).each do |item|
|
341
|
+
items[item['name']] = URI(item['href']).path.gsub(
|
342
|
+
'/api/vAppTemplate/vappTemplate-', ''
|
343
|
+
)
|
344
|
+
end
|
345
|
+
{ :description => description, :items => items }
|
346
|
+
end
|
347
|
+
|
348
|
+
##
|
349
|
+
# friendly helper method to fetch an catalogItem by name
|
350
|
+
# - catalogId (use get_catalog_name(org, name))
|
351
|
+
# - catalagItemName
|
352
|
+
def get_catalog_item_by_name(catalog_id, catalog_item_name)
|
353
|
+
result = nil
|
354
|
+
catalog_elems = get_catalog(catalog_id)
|
355
|
+
|
356
|
+
catalog_elems[:items].each do |catalog_elem|
|
357
|
+
|
358
|
+
catalog_item = get_catalog_item(catalog_elem[1])
|
359
|
+
if catalog_item[:items][catalog_item_name]
|
360
|
+
# This is a vApp Catalog Item
|
361
|
+
|
362
|
+
# fetch CatalogItemId
|
363
|
+
catalog_item_id = catalog_item[:items][catalog_item_name]
|
364
|
+
|
365
|
+
# Fetch the catalogItemId information
|
366
|
+
params = {
|
367
|
+
'method' => :get,
|
368
|
+
'command' => "/vAppTemplate/vappTemplate-#{catalog_item_id}"
|
369
|
+
}
|
370
|
+
response, _headers = send_request(params)
|
371
|
+
|
372
|
+
# VMs Hash for all the vApp VM entities
|
373
|
+
vms_hash = {}
|
374
|
+
response.css('/VAppTemplate/Children/Vm').each do |vm_elem|
|
375
|
+
vm_name = vm_elem['name']
|
376
|
+
vm_id = URI(vm_elem['href']).path.gsub(
|
377
|
+
'/api/vAppTemplate/vm-', ''
|
378
|
+
)
|
379
|
+
|
380
|
+
# Add the VM name/id to the VMs Hash
|
381
|
+
vms_hash[vm_name] = { :id => vm_id }
|
382
|
+
end
|
383
|
+
result = {
|
384
|
+
catalog_item_name => catalog_item_id, :vms_hash => vms_hash
|
385
|
+
}
|
386
|
+
end
|
387
|
+
end
|
388
|
+
result
|
389
|
+
end
|
390
|
+
|
391
|
+
##
|
392
|
+
# Fetch details about a given vapp:
|
393
|
+
# - name
|
394
|
+
# - description
|
395
|
+
# - status
|
396
|
+
# - IP
|
397
|
+
# - Children VMs:
|
398
|
+
# -- IP addresses
|
399
|
+
# -- status
|
400
|
+
# -- ID
|
401
|
+
def get_vapp(vapp_id)
|
402
|
+
params = {
|
403
|
+
'method' => :get,
|
404
|
+
'command' => "/vApp/vapp-#{vapp_id}"
|
405
|
+
}
|
406
|
+
|
407
|
+
response, _headers = send_request(params)
|
408
|
+
|
409
|
+
vapp_node = response.css('VApp').first
|
410
|
+
if vapp_node
|
411
|
+
name = vapp_node['name']
|
412
|
+
status = convert_vapp_status(vapp_node['status'])
|
413
|
+
end
|
414
|
+
|
415
|
+
description = response.css('Description').first
|
416
|
+
description = description.text unless description.nil?
|
417
|
+
|
418
|
+
ip = response.css('IpAddress').first
|
419
|
+
ip = ip.text unless ip.nil?
|
420
|
+
|
421
|
+
vms = response.css('Children Vm')
|
422
|
+
vms_hash = {}
|
423
|
+
|
424
|
+
# ipAddress could be namespaced or not:
|
425
|
+
# see https://github.com/astratto/vcloud-rest/issues/3
|
426
|
+
vms.each do |vm|
|
427
|
+
vapp_local_id = vm.css('VAppScopedLocalId')
|
428
|
+
addresses = vm.css('rasd|Connection').collect {
|
429
|
+
|n| n['vcloud:ipAddress'] || n['ipAddress']
|
430
|
+
}
|
431
|
+
vms_hash[vm['name'].to_sym] = {
|
432
|
+
:addresses => addresses,
|
433
|
+
:status => convert_vapp_status(vm['status']),
|
434
|
+
:id => URI(vm['href']).path.gsub('/api/vApp/vm-', ''),
|
435
|
+
:vapp_scoped_local_id => vapp_local_id.text
|
436
|
+
}
|
437
|
+
end
|
438
|
+
|
439
|
+
# TODO: EXPAND INFO FROM RESPONSE
|
440
|
+
{
|
441
|
+
:name => name,
|
442
|
+
:description => description,
|
443
|
+
:status => status,
|
444
|
+
:ip => ip,
|
445
|
+
:vms_hash => vms_hash
|
446
|
+
}
|
447
|
+
end
|
448
|
+
|
449
|
+
##
|
450
|
+
# Delete a given vapp
|
451
|
+
# NOTE: It doesn't verify that the vapp is shutdown
|
452
|
+
def delete_vapp(vapp_id)
|
453
|
+
params = {
|
454
|
+
'method' => :delete,
|
455
|
+
'command' => "/vApp/vapp-#{vapp_id}"
|
456
|
+
}
|
457
|
+
|
458
|
+
_response, headers = send_request(params)
|
459
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
460
|
+
task_id
|
461
|
+
end
|
462
|
+
|
463
|
+
##
|
464
|
+
# Shutdown a given vapp
|
465
|
+
def poweroff_vapp(vapp_id)
|
466
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
467
|
+
xml.UndeployVAppParams(
|
468
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5'
|
469
|
+
) { xml.UndeployPowerAction 'powerOff' }
|
470
|
+
end
|
471
|
+
|
472
|
+
params = {
|
473
|
+
'method' => :post,
|
474
|
+
'command' => "/vApp/vapp-#{vapp_id}/action/undeploy"
|
475
|
+
}
|
476
|
+
|
477
|
+
_response, headers = send_request(
|
478
|
+
params,
|
479
|
+
builder.to_xml,
|
480
|
+
'application/vnd.vmware.vcloud.undeployVAppParams+xml'
|
481
|
+
)
|
482
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
483
|
+
task_id
|
484
|
+
end
|
485
|
+
|
486
|
+
##
|
487
|
+
# Suspend a given vapp
|
488
|
+
def suspend_vapp(vapp_id)
|
489
|
+
params = {
|
490
|
+
'method' => :post,
|
491
|
+
'command' => "/vApp/vapp-#{vapp_id}/power/action/suspend"
|
492
|
+
}
|
493
|
+
|
494
|
+
_response, headers = send_request(params)
|
495
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
496
|
+
task_id
|
497
|
+
end
|
498
|
+
|
499
|
+
##
|
500
|
+
# reboot a given vapp
|
501
|
+
# This will basically initial a guest OS reboot, and will only work if
|
502
|
+
# VMware-tools are installed on the underlying VMs.
|
503
|
+
# vShield Edge devices are not affected
|
504
|
+
def reboot_vapp(vapp_id)
|
505
|
+
params = {
|
506
|
+
'method' => :post,
|
507
|
+
'command' => "/vApp/vapp-#{vapp_id}/power/action/reboot"
|
508
|
+
}
|
509
|
+
|
510
|
+
_response, headers = send_request(params)
|
511
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
512
|
+
task_id
|
513
|
+
end
|
514
|
+
|
515
|
+
##
|
516
|
+
# reset a given vapp
|
517
|
+
# This will basically reset the VMs within the vApp
|
518
|
+
# vShield Edge devices are not affected.
|
519
|
+
def reset_vapp(vapp_id)
|
520
|
+
params = {
|
521
|
+
'method' => :post,
|
522
|
+
'command' => "/vApp/vapp-#{vapp_id}/power/action/reset"
|
523
|
+
}
|
524
|
+
|
525
|
+
_response, headers = send_request(params)
|
526
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
527
|
+
task_id
|
528
|
+
end
|
529
|
+
|
530
|
+
##
|
531
|
+
# Boot a given vapp
|
532
|
+
def poweron_vapp(vapp_id)
|
533
|
+
params = {
|
534
|
+
'method' => :post,
|
535
|
+
'command' => "/vApp/vapp-#{vapp_id}/power/action/powerOn"
|
536
|
+
}
|
537
|
+
|
538
|
+
_response, headers = send_request(params)
|
539
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
540
|
+
task_id
|
541
|
+
end
|
542
|
+
|
543
|
+
#### VM operations ####
|
544
|
+
##
|
545
|
+
# Delete a given vm
|
546
|
+
# NOTE: It doesn't verify that the vm is shutdown
|
547
|
+
def delete_vm(vm_id)
|
548
|
+
params = {
|
549
|
+
'method' => :delete,
|
550
|
+
'command' => "/vApp/vm-#{vm_id}"
|
551
|
+
}
|
552
|
+
|
553
|
+
_response, headers = send_request(params)
|
554
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
555
|
+
task_id
|
556
|
+
end
|
557
|
+
|
558
|
+
##
|
559
|
+
# Shutdown a given VM
|
560
|
+
# Using undeploy as a REAL powerOff
|
561
|
+
# Only poweroff will put the VM into a partially powered off state.
|
562
|
+
def poweroff_vm(vm_id)
|
563
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
564
|
+
xml.UndeployVAppParams(
|
565
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5'
|
566
|
+
) { xml.UndeployPowerAction 'powerOff' }
|
567
|
+
end
|
568
|
+
|
569
|
+
params = {
|
570
|
+
'method' => :post,
|
571
|
+
'command' => "/vApp/vm-#{vm_id}/action/undeploy"
|
572
|
+
}
|
573
|
+
|
574
|
+
_response, headers = send_request(
|
575
|
+
params,
|
576
|
+
builder.to_xml,
|
577
|
+
'application/vnd.vmware.vcloud.undeployVAppParams+xml'
|
578
|
+
)
|
579
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
580
|
+
task_id
|
581
|
+
end
|
582
|
+
|
583
|
+
##
|
584
|
+
# Suspend a given VM
|
585
|
+
def suspend_vm(vm_id)
|
586
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
587
|
+
xml.UndeployVAppParams(
|
588
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5'
|
589
|
+
) { xml.UndeployPowerAction 'suspend' }
|
590
|
+
end
|
591
|
+
|
592
|
+
params = {
|
593
|
+
'method' => :post,
|
594
|
+
'command' => "/vApp/vm-#{vm_id}/action/undeploy"
|
595
|
+
}
|
596
|
+
|
597
|
+
_response, headers = send_request(
|
598
|
+
params,
|
599
|
+
builder.to_xml,
|
600
|
+
'application/vnd.vmware.vcloud.undeployVAppParams+xml'
|
601
|
+
)
|
602
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
603
|
+
task_id
|
604
|
+
end
|
605
|
+
|
606
|
+
##
|
607
|
+
# reboot a given VM
|
608
|
+
# This will basically initial a guest OS reboot, and will only work if
|
609
|
+
# VMware-tools are installed on the underlying VMs.
|
610
|
+
# vShield Edge devices are not affected
|
611
|
+
def reboot_vm(vm_id)
|
612
|
+
params = {
|
613
|
+
'method' => :post,
|
614
|
+
'command' => "/vApp/vm-#{vm_id}/power/action/reboot"
|
615
|
+
}
|
616
|
+
|
617
|
+
_response, headers = send_request(params)
|
618
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
619
|
+
task_id
|
620
|
+
end
|
621
|
+
|
622
|
+
##
|
623
|
+
# reset a given VM
|
624
|
+
# This will basically reset the VMs within the vApp
|
625
|
+
# vShield Edge devices are not affected.
|
626
|
+
def reset_vm(vm_id)
|
627
|
+
params = {
|
628
|
+
'method' => :post,
|
629
|
+
'command' => "/vApp/vm-#{vm_id}/power/action/reset"
|
630
|
+
}
|
631
|
+
|
632
|
+
_response, headers = send_request(params)
|
633
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
634
|
+
task_id
|
635
|
+
end
|
636
|
+
|
637
|
+
##
|
638
|
+
# Boot a given VM
|
639
|
+
def poweron_vm(vm_id)
|
640
|
+
params = {
|
641
|
+
'method' => :post,
|
642
|
+
'command' => "/vApp/vm-#{vm_id}/power/action/powerOn"
|
643
|
+
}
|
644
|
+
|
645
|
+
_response, headers = send_request(params)
|
646
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
647
|
+
task_id
|
648
|
+
end
|
649
|
+
|
650
|
+
##
|
651
|
+
# Create a catalog in an organization
|
652
|
+
def create_catalog(org_id, catalog_name, catalog_description)
|
653
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
654
|
+
xml.AdminCatalog(
|
655
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5',
|
656
|
+
'name' => catalog_name
|
657
|
+
) { xml.Description catalog_description }
|
658
|
+
|
659
|
+
end
|
660
|
+
|
661
|
+
params = {
|
662
|
+
'method' => :post,
|
663
|
+
'command' => "/admin/org/#{org_id}/catalogs"
|
664
|
+
}
|
665
|
+
|
666
|
+
response, _headers = send_request(
|
667
|
+
params,
|
668
|
+
builder.to_xml,
|
669
|
+
'application/vnd.vmware.admin.catalog+xml'
|
670
|
+
)
|
671
|
+
task_id = URI(response.css(
|
672
|
+
"AdminCatalog Tasks Task[operationName='catalogCreateCatalog']"
|
673
|
+
).first[:href]).path.gsub('/api/task/', '')
|
674
|
+
|
675
|
+
catalog_id = URI(response.css(
|
676
|
+
"AdminCatalog Link [type='application/vnd.vmware.vcloud.catalog+xml']"
|
677
|
+
).first[:href]).path.gsub('/api/catalog/', '')
|
678
|
+
|
679
|
+
{ :task_id => task_id, :catalog_id => catalog_id }
|
680
|
+
end
|
681
|
+
|
682
|
+
##
|
683
|
+
# Create a vapp starting from a template
|
684
|
+
#
|
685
|
+
# Params:
|
686
|
+
# - vdc: the associated VDC
|
687
|
+
# - vapp_name: name of the target vapp
|
688
|
+
# - vapp_description: description of the target vapp
|
689
|
+
# - vapp_templateid: ID of the vapp template
|
690
|
+
def create_vapp_from_template(vdc, vapp_name, vapp_description, vapp_template_id, poweron = false)
|
691
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
692
|
+
xml.InstantiateVAppTemplateParams(
|
693
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5',
|
694
|
+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
|
695
|
+
'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1',
|
696
|
+
'name' => vapp_name,
|
697
|
+
'deploy' => 'true',
|
698
|
+
'powerOn' => poweron
|
699
|
+
) { xml.Description vapp_description xml.Source(
|
700
|
+
'href' => "#{@api_url}/vAppTemplate/#{vapp_template_id}"
|
701
|
+
)
|
702
|
+
}
|
703
|
+
end
|
704
|
+
|
705
|
+
params = {
|
706
|
+
'method' => :post,
|
707
|
+
'command' => "/vdc/#{vdc}/action/instantiateVAppTemplate"
|
708
|
+
}
|
709
|
+
|
710
|
+
response, headers = send_request(
|
711
|
+
params,
|
712
|
+
builder.to_xml,
|
713
|
+
'application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml'
|
714
|
+
)
|
715
|
+
|
716
|
+
vapp_id = URI(headers['Location']).path.gsub('/api/vApp/vapp-', '')
|
717
|
+
|
718
|
+
task = response.css(
|
719
|
+
"VApp Task[operationName='vdcInstantiateVapp']"
|
720
|
+
).first
|
721
|
+
|
722
|
+
task_id = URI(task['href']).path.gsub("/api/task/", '')
|
723
|
+
|
724
|
+
{ :vapp_id => vapp_id, :task_id => task_id }
|
725
|
+
end
|
726
|
+
|
727
|
+
##
|
728
|
+
# Compose a vapp using existing virtual machines
|
729
|
+
#
|
730
|
+
# Params:
|
731
|
+
# - vdc: the associated VDC
|
732
|
+
# - vapp_name: name of the target vapp
|
733
|
+
# - vapp_description: description of the target vapp
|
734
|
+
# - vm_list: hash with IDs of the VMs used in the composing process
|
735
|
+
# - network_config: hash of the network configuration for the vapp
|
736
|
+
def compose_vapp_from_vm(vdc, vapp_name, vapp_description, vm_list = {}, network_config = {})
|
737
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
738
|
+
xml.ComposeVAppParams(
|
739
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5',
|
740
|
+
'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1',
|
741
|
+
'name' => vapp_name,
|
742
|
+
'deploy' => 'false',
|
743
|
+
'powerOn' => 'false') {
|
744
|
+
xml.Description vapp_description
|
745
|
+
xml.InstantiationParams {
|
746
|
+
xml.NetworkConfigSection {
|
747
|
+
xml['ovf'].Info 'Configuration parameters for logical networks'
|
748
|
+
xml.NetworkConfig('networkName' => network_config[:name]) {
|
749
|
+
xml.Configuration {
|
750
|
+
if network_config[:fence_mode] != 'bridged'
|
751
|
+
xml.IpScopes {
|
752
|
+
xml.IpScope {
|
753
|
+
xml.IsInherited(network_config[:is_inherited] || 'false')
|
754
|
+
xml.Gateway network_config[:gateway]
|
755
|
+
xml.Netmask network_config[:netmask]
|
756
|
+
xml.Dns1 network_config[:dns1] if network_config[:dns1]
|
757
|
+
xml.Dns2 network_config[:dns2] if network_config[:dns2]
|
758
|
+
xml.DnsSuffix network_config[:dns_suffix] if network_config[:dns_suffix]
|
759
|
+
xml.IpRanges {
|
760
|
+
xml.IpRange {
|
761
|
+
xml.StartAddress network_config[:start_address]
|
762
|
+
xml.EndAddress network_config[:end_address]
|
763
|
+
}
|
764
|
+
}
|
765
|
+
}
|
766
|
+
}
|
767
|
+
end
|
768
|
+
xml.ParentNetwork("href" => "#{@api_url}/network/#{network_config[:parent_network]}")
|
769
|
+
xml.FenceMode network_config[:fence_mode]
|
770
|
+
if network_config[:fence_mode] != 'bridged'
|
771
|
+
xml.Features {
|
772
|
+
xml.FirewallService {
|
773
|
+
xml.IsEnabled(network_config[:enable_firewall] || "false")
|
774
|
+
}
|
775
|
+
xml.NatService {
|
776
|
+
xml.IsEnabled "true"
|
777
|
+
xml.NatType "portForwarding"
|
778
|
+
xml.Policy(network_config[:nat_policy_type] || "allowTraffic")
|
779
|
+
}
|
780
|
+
}
|
781
|
+
end
|
782
|
+
}
|
783
|
+
}
|
784
|
+
}
|
785
|
+
}
|
786
|
+
vm_list.each do |vm_name, vm_id|
|
787
|
+
xml.SourcedItem {
|
788
|
+
xml.Source('href' => "#{@api_url}/vAppTemplate/vm-#{vm_id}", 'name' => vm_name)
|
789
|
+
xml.InstantiationParams {
|
790
|
+
xml.NetworkConnectionSection(
|
791
|
+
'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1',
|
792
|
+
'type' => 'application/vnd.vmware.vcloud.networkConnectionSection+xml',
|
793
|
+
'href' => "#{@api_url}/vAppTemplate/vm-#{vm_id}/networkConnectionSection/") {
|
794
|
+
xml['ovf'].Info 'Network config for sourced item'
|
795
|
+
xml.PrimaryNetworkConnectionIndex '0'
|
796
|
+
xml.NetworkConnection('network' => network_config[:name]) {
|
797
|
+
xml.NetworkConnectionIndex '0'
|
798
|
+
xml.IsConnected 'true'
|
799
|
+
xml.IpAddressAllocationMode(network_config[:ip_allocation_mode] || 'POOL')
|
800
|
+
}
|
801
|
+
}
|
802
|
+
}
|
803
|
+
xml.NetworkAssignment('containerNetwork' => network_config[:name], 'innerNetwork' => network_config[:name])
|
804
|
+
}
|
805
|
+
end
|
806
|
+
xml.AllEULAsAccepted 'true'
|
807
|
+
}
|
808
|
+
end
|
809
|
+
|
810
|
+
params = {
|
811
|
+
'method' => :post,
|
812
|
+
'command' => "/vdc/#{vdc}/action/composeVApp"
|
813
|
+
}
|
814
|
+
|
815
|
+
response, headers = send_request(
|
816
|
+
params,
|
817
|
+
builder.to_xml,
|
818
|
+
'application/vnd.vmware.vcloud.composeVAppParams+xml'
|
819
|
+
)
|
820
|
+
|
821
|
+
vapp_id = URI(headers['Location']).path.gsub("/api/vApp/vapp-", '')
|
822
|
+
|
823
|
+
task = response.css("VApp Task[operationName='vdcComposeVapp']").first
|
824
|
+
task_id = URI(task['href']).path.gsub('/api/task/', '')
|
825
|
+
|
826
|
+
{ :vapp_id => vapp_id, :task_id => task_id }
|
827
|
+
end
|
828
|
+
|
829
|
+
##
|
830
|
+
# Recompose an existing vapp using existing virtual machines
|
831
|
+
#
|
832
|
+
# Params:
|
833
|
+
# - vdc: the associated VDC
|
834
|
+
# - vapp_name: name of the target vapp
|
835
|
+
# - vapp_description: description of the target vapp
|
836
|
+
# - vm_list: hash with IDs of the VMs to be used in the composing process
|
837
|
+
# - network_config: hash of the network configuration for the vapp
|
838
|
+
|
839
|
+
def recompose_vapp_from_vm(vapp_id, vm_list = {}, network_config = {})
|
840
|
+
original_vapp = get_vapp(vapp_id)
|
841
|
+
|
842
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
843
|
+
xml.RecomposeVAppParams(
|
844
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5',
|
845
|
+
'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1',
|
846
|
+
'name' => original_vapp[:name]) {
|
847
|
+
xml.Description original_vapp[:description]
|
848
|
+
xml.InstantiationParams {}
|
849
|
+
vm_list.each do |vm_name, vm_id|
|
850
|
+
xml.SourcedItem {
|
851
|
+
xml.Source('href' => "#{@api_url}/vAppTemplate/vm-#{vm_id}", 'name' => vm_name)
|
852
|
+
xml.InstantiationParams {
|
853
|
+
xml.NetworkConnectionSection(
|
854
|
+
'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1',
|
855
|
+
'type' => 'application/vnd.vmware.vcloud.networkConnectionSection+xml',
|
856
|
+
'href' => "#{@api_url}/vAppTemplate/vm-#{vm_id}/networkConnectionSection/") {
|
857
|
+
xml['ovf'].Info 'Network config for sourced item'
|
858
|
+
xml.PrimaryNetworkConnectionIndex '0'
|
859
|
+
xml.NetworkConnection('network' => network_config[:name]) {
|
860
|
+
xml.NetworkConnectionIndex '0'
|
861
|
+
xml.IsConnected 'true'
|
862
|
+
xml.IpAddressAllocationMode(network_config[:ip_allocation_mode] || 'POOL')
|
863
|
+
}
|
864
|
+
}
|
865
|
+
}
|
866
|
+
xml.NetworkAssignment('containerNetwork' => network_config[:name], 'innerNetwork' => network_config[:name])
|
867
|
+
}
|
868
|
+
end
|
869
|
+
xml.AllEULAsAccepted 'true'
|
870
|
+
}
|
871
|
+
end
|
872
|
+
|
873
|
+
params = {
|
874
|
+
'method' => :post,
|
875
|
+
'command' => "/vApp/vapp-#{vapp_id}/action/recomposeVApp"
|
876
|
+
}
|
877
|
+
|
878
|
+
response, headers = send_request(
|
879
|
+
params,
|
880
|
+
builder.to_xml,
|
881
|
+
'application/vnd.vmware.vcloud.recomposeVAppParams+xml'
|
882
|
+
)
|
883
|
+
|
884
|
+
vapp_id = URI(headers['Location']).path.gsub('/api/vApp/vapp-', '')
|
885
|
+
|
886
|
+
task = response.css("Task [operationName='vdcRecomposeVapp']").first
|
887
|
+
task_id = URI(task['href']).path.gsub('/api/task/', '')
|
888
|
+
|
889
|
+
{ :vapp_id => vapp_id, :task_id => task_id }
|
890
|
+
end
|
891
|
+
|
892
|
+
# Fetch details about a given vapp template:
|
893
|
+
# - name
|
894
|
+
# - description
|
895
|
+
# - Children VMs:
|
896
|
+
# -- ID
|
897
|
+
def get_vapp_template(vapp_id)
|
898
|
+
params = {
|
899
|
+
'method' => :get,
|
900
|
+
'command' => "/vAppTemplate/vappTemplate-#{vapp_id}"
|
901
|
+
}
|
902
|
+
|
903
|
+
response, _headers = send_request(params)
|
904
|
+
|
905
|
+
vapp_node = response.css('VAppTemplate').first
|
906
|
+
if vapp_node
|
907
|
+
name = vapp_node['name']
|
908
|
+
convert_vapp_status(vapp_node['status'])
|
909
|
+
end
|
910
|
+
|
911
|
+
description = response.css('Description').first
|
912
|
+
description = description.text unless description.nil?
|
913
|
+
|
914
|
+
vms = response.css('Children Vm')
|
915
|
+
vms_hash = {}
|
916
|
+
|
917
|
+
vms.each do |vm|
|
918
|
+
vms_hash[vm['name']] = {
|
919
|
+
:id => URI(vm['href']).path.gsub('/api/vAppTemplate/vm-', '')
|
920
|
+
}
|
921
|
+
end
|
922
|
+
|
923
|
+
{ :name => name, :description => description, :vms_hash => vms_hash }
|
924
|
+
end
|
925
|
+
|
926
|
+
##
|
927
|
+
# Set vApp port forwarding rules
|
928
|
+
#
|
929
|
+
# - vapp_id: id of the vapp to be modified
|
930
|
+
# - network_name: name of the vapp network to be modified
|
931
|
+
# - config: hash with network configuration specifications, must
|
932
|
+
# contain an array inside :nat_rules with the nat rules to be
|
933
|
+
# applied.
|
934
|
+
def set_vapp_port_forwarding_rules(vapp_id, network_name, config = {})
|
935
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
936
|
+
xml.NetworkConfigSection(
|
937
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5',
|
938
|
+
'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1') {
|
939
|
+
xml['ovf'].Info 'Network configuration'
|
940
|
+
xml.NetworkConfig('networkName' => network_name) {
|
941
|
+
xml.Configuration {
|
942
|
+
xml.ParentNetwork('href' => "#{@api_url}/network/#{config[:parent_network]}")
|
943
|
+
xml.FenceMode(config[:fence_mode] || 'isolated')
|
944
|
+
xml.Features {
|
945
|
+
xml.NatService {
|
946
|
+
xml.IsEnabled 'true'
|
947
|
+
xml.NatType 'portForwarding'
|
948
|
+
xml.Policy(config[:nat_policy_type] || 'allowTraffic')
|
949
|
+
config[:nat_rules].each do |nat_rule|
|
950
|
+
xml.NatRule {
|
951
|
+
xml.VmRule {
|
952
|
+
xml.ExternalPort nat_rule[:nat_external_port]
|
953
|
+
xml.VAppScopedVmId nat_rule[:vapp_scoped_local_id]
|
954
|
+
xml.VmNicId(nat_rule[:nat_vmnic_id] || '0')
|
955
|
+
xml.InternalPort nat_rule[:nat_internal_port]
|
956
|
+
xml.Protocol(nat_rule[:nat_protocol] || 'TCP')
|
957
|
+
}
|
958
|
+
}
|
959
|
+
end
|
960
|
+
}
|
961
|
+
}
|
962
|
+
}
|
963
|
+
}
|
964
|
+
}
|
965
|
+
end
|
966
|
+
|
967
|
+
params = {
|
968
|
+
'method' => :put,
|
969
|
+
'command' => "/vApp/vapp-#{vapp_id}/networkConfigSection"
|
970
|
+
}
|
971
|
+
|
972
|
+
_response, headers = send_request(
|
973
|
+
params,
|
974
|
+
builder.to_xml,
|
975
|
+
'application/vnd.vmware.vcloud.networkConfigSection+xml'
|
976
|
+
)
|
977
|
+
|
978
|
+
task_id = URI(headers['Location']).path.gsub("/api/task/", '')
|
979
|
+
task_id
|
980
|
+
end
|
981
|
+
|
982
|
+
##
|
983
|
+
# Add vApp port forwarding rules
|
984
|
+
#
|
985
|
+
# - vapp_id: id of the vapp to be modified
|
986
|
+
# - network_name: name of the vapp network to be modified
|
987
|
+
# - config: hash with network configuration specifications,
|
988
|
+
# must contain an array inside :nat_rules with the nat rules to add.
|
989
|
+
# nat_rules << {
|
990
|
+
# :nat_external_port => j.to_s,
|
991
|
+
# :nat_internal_port => "22",
|
992
|
+
# :nat_protocol => "TCP",
|
993
|
+
# :vm_scoped_local_id => value[:vapp_scoped_local_id]
|
994
|
+
# }
|
995
|
+
|
996
|
+
def add_vapp_port_forwarding_rules(vapp_id, network_name, config = {})
|
997
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
998
|
+
xml.NetworkConfigSection(
|
999
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5',
|
1000
|
+
'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1') {
|
1001
|
+
xml['ovf'].Info 'Network configuration'
|
1002
|
+
xml.NetworkConfig('networkName' => network_name) {
|
1003
|
+
xml.Configuration {
|
1004
|
+
xml.ParentNetwork('href' => "#{@api_url}/network/#{config[:parent_network]}")
|
1005
|
+
xml.FenceMode(config[:fence_mode] || 'isolated')
|
1006
|
+
xml.Features {
|
1007
|
+
xml.NatService {
|
1008
|
+
xml.IsEnabled 'true'
|
1009
|
+
xml.NatType 'portForwarding'
|
1010
|
+
xml.Policy(config[:nat_policy_type] || 'allowTraffic')
|
1011
|
+
|
1012
|
+
pre_existing = get_vapp_port_forwarding_rules(vapp_id)
|
1013
|
+
|
1014
|
+
config[:nat_rules].concat(pre_existing)
|
1015
|
+
|
1016
|
+
config[:nat_rules].each do |nat_rule|
|
1017
|
+
xml.NatRule {
|
1018
|
+
xml.VmRule {
|
1019
|
+
xml.ExternalPort nat_rule[:nat_external_port]
|
1020
|
+
xml.VAppScopedVmId nat_rule[:vapp_scoped_local_id]
|
1021
|
+
xml.VmNicId(nat_rule[:nat_vmnic_id] || '0')
|
1022
|
+
xml.InternalPort nat_rule[:nat_internal_port]
|
1023
|
+
xml.Protocol(nat_rule[:nat_protocol] || 'TCP')
|
1024
|
+
}
|
1025
|
+
}
|
1026
|
+
end
|
1027
|
+
}
|
1028
|
+
}
|
1029
|
+
}
|
1030
|
+
}
|
1031
|
+
}
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
params = {
|
1035
|
+
'method' => :put,
|
1036
|
+
'command' => "/vApp/vapp-#{vapp_id}/networkConfigSection"
|
1037
|
+
}
|
1038
|
+
|
1039
|
+
_response, headers = send_request(
|
1040
|
+
params,
|
1041
|
+
builder.to_xml,
|
1042
|
+
'application/vnd.vmware.vcloud.networkConfigSection+xml'
|
1043
|
+
)
|
1044
|
+
|
1045
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
1046
|
+
task_id
|
1047
|
+
end
|
1048
|
+
##
|
1049
|
+
# Get vApp port forwarding rules
|
1050
|
+
#
|
1051
|
+
# - vapp_id: id of the vApp
|
1052
|
+
def get_vapp_port_forwarding_rules(vapp_id)
|
1053
|
+
params = {
|
1054
|
+
'method' => :get,
|
1055
|
+
'command' => "/vApp/vapp-#{vapp_id}/networkConfigSection"
|
1056
|
+
}
|
1057
|
+
|
1058
|
+
response, _headers = send_request(params)
|
1059
|
+
|
1060
|
+
# FIXME: this will return nil if the vApp uses multiple vApp Networks
|
1061
|
+
# with Edge devices in natRouted/portForwarding mode.
|
1062
|
+
config = response.css(
|
1063
|
+
'NetworkConfigSection/NetworkConfig/Configuration'
|
1064
|
+
)
|
1065
|
+
fence_mode = config.css('/FenceMode').text
|
1066
|
+
nat_type = config.css('/Features/NatService/NatType').text
|
1067
|
+
|
1068
|
+
unless fence_mode == 'natRouted'
|
1069
|
+
fail Errors::InvalidStateError,
|
1070
|
+
'Invalid request because FenceMode must be natRouted.'
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
unless nat_type == 'portForwarding'
|
1074
|
+
fail Errors::InvalidStateError,
|
1075
|
+
'Invalid request because NatType must be portForwarding.'
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
nat_rules = []
|
1079
|
+
config.css('/Features/NatService/NatRule').each do |rule|
|
1080
|
+
# portforwarding rules information
|
1081
|
+
vm_rule = rule.css('VmRule')
|
1082
|
+
|
1083
|
+
nat_rules << {
|
1084
|
+
:nat_external_ip => vm_rule.css('ExternalIpAddress').text,
|
1085
|
+
:nat_external_port => vm_rule.css('ExternalPort').text,
|
1086
|
+
:vapp_scoped_local_id => vm_rule.css('VAppScopedVmId').text,
|
1087
|
+
:vm_nic_id => vm_rule.css('VmNicId').text,
|
1088
|
+
:nat_internal_port => vm_rule.css('InternalPort').text,
|
1089
|
+
:nat_protocol => vm_rule.css('Protocol').text
|
1090
|
+
}
|
1091
|
+
end
|
1092
|
+
nat_rules
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
##
|
1096
|
+
# Find an edge gateway id from the edge name and vdc_id
|
1097
|
+
#
|
1098
|
+
# - edge_gateway_name: Name of the vSE
|
1099
|
+
# - vdc_id: virtual datacenter id
|
1100
|
+
#
|
1101
|
+
def find_edge_gateway_id(edge_gateway_name, vdc_id)
|
1102
|
+
params = {
|
1103
|
+
'method' => :get,
|
1104
|
+
'command' => "/admin/vdc/#{vdc_id}/edgeGateways"
|
1105
|
+
}
|
1106
|
+
|
1107
|
+
response, _headers = send_request(params)
|
1108
|
+
edge_gateways = response.css('EdgeGatewayRecord')
|
1109
|
+
|
1110
|
+
edge_gateways.each do |edge_gateway|
|
1111
|
+
if edge_gateway['name'] == edge_gateway_name
|
1112
|
+
@edge_gateway_id = URI(edge_gateway['href']).path.gsub(
|
1113
|
+
'/api/admin/edgeGateway/', '')
|
1114
|
+
end
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
fail Errors::EdgeGWNotFound, :message => edge_gateway_name if @edge_gateway_id.nil?
|
1118
|
+
|
1119
|
+
@edge_gateway_id
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
##
|
1123
|
+
# Redeploy the vShield Edge Gateway VM, due to some knowns issues
|
1124
|
+
# where the current rules are not "applied" and the EdgeGW is in an
|
1125
|
+
# unmanageable state.
|
1126
|
+
#
|
1127
|
+
def redeploy_edge_gateway(edge_gateway_id)
|
1128
|
+
params = {
|
1129
|
+
'method' => :post,
|
1130
|
+
'command' => "/admin/edgeGateway/#{edge_gateway_id}/action/redeploy"
|
1131
|
+
}
|
1132
|
+
|
1133
|
+
_response, headers = send_request(params)
|
1134
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
1135
|
+
task_id
|
1136
|
+
end
|
1137
|
+
|
1138
|
+
##
|
1139
|
+
# Find an edge gateway network from the edge name and vdc_id, and ip
|
1140
|
+
#
|
1141
|
+
# - edge_gateway_name: Name of the vSE
|
1142
|
+
# - vdc_id: virtual datacenter id
|
1143
|
+
# - edge_gateway_ip: public ip associated to that vSE
|
1144
|
+
#
|
1145
|
+
|
1146
|
+
def find_edge_gateway_network(edge_gateway_name, vdc_id, edge_gateway_ip)
|
1147
|
+
edge_gateway_id = find_edge_gateway_id(edge_gateway_name, vdc_id)
|
1148
|
+
|
1149
|
+
params = {
|
1150
|
+
'method' => :get,
|
1151
|
+
'command' => "/admin/edgeGateway/#{edge_gateway_id}"
|
1152
|
+
}
|
1153
|
+
|
1154
|
+
response, _headers = send_request(params)
|
1155
|
+
response.css(
|
1156
|
+
'EdgeGateway Configuration GatewayInterfaces GatewayInterface'
|
1157
|
+
).each do |gw|
|
1158
|
+
# Only check uplinks, avoid another check
|
1159
|
+
if gw.css('InterfaceType').text == 'uplink'
|
1160
|
+
|
1161
|
+
# Loop on all sub-allocation pools
|
1162
|
+
gw.css('SubnetParticipation IpRanges IpRange').each do |cur_range|
|
1163
|
+
|
1164
|
+
low_ip = cur_range.css('StartAddress').first.text
|
1165
|
+
high_ip = cur_range.css('EndAddress').first.text
|
1166
|
+
|
1167
|
+
range_ip_low = NetAddr.ip_to_i(low_ip)
|
1168
|
+
range_ip_high = NetAddr.ip_to_i(high_ip)
|
1169
|
+
@test_ip = NetAddr.ip_to_i(edge_gateway_ip)
|
1170
|
+
|
1171
|
+
@logger.debug("Our IP range: #{range_ip_low..range_ip_high}")
|
1172
|
+
@logger.debug("Our Test IP #{@test_ip}")
|
1173
|
+
|
1174
|
+
case @test_ip
|
1175
|
+
when range_ip_low..range_ip_high then
|
1176
|
+
@logger.debug("Found IP #{NetAddr.i_to_ip(@test_ip)}")
|
1177
|
+
@network_id = gw.css('Network').first[:href]
|
1178
|
+
end
|
1179
|
+
end
|
1180
|
+
end
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
fail Errors::EdgeGWIPNotFound,
|
1184
|
+
:message => NetAddr.i_to_ip(@test_ip) if @network_id.nil?
|
1185
|
+
|
1186
|
+
@network_id
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
##
|
1190
|
+
# Check wether the specified Network is connected to the specified
|
1191
|
+
# Organization Edge
|
1192
|
+
#
|
1193
|
+
# - edge_gateway_name: Name of the vSE
|
1194
|
+
# - vdc_id: virtual datacenter id
|
1195
|
+
# - vdc_network_name: VDC Network Name to check
|
1196
|
+
#
|
1197
|
+
|
1198
|
+
def check_edge_gateway_network(edge_gateway_name, vdc_id, vdc_network_name)
|
1199
|
+
edge_gateway_id = find_edge_gateway_id(edge_gateway_name, vdc_id)
|
1200
|
+
|
1201
|
+
params = {
|
1202
|
+
'method' => :get,
|
1203
|
+
'command' => "/admin/edgeGateway/#{edge_gateway_id}"
|
1204
|
+
}
|
1205
|
+
|
1206
|
+
response, _headers = send_request(params)
|
1207
|
+
response.css(
|
1208
|
+
'EdgeGateway Configuration GatewayInterfaces GatewayInterface'
|
1209
|
+
).each do |gw|
|
1210
|
+
# Only check internal connections, avoid another check
|
1211
|
+
if gw.css('InterfaceType').text == 'internal'
|
1212
|
+
|
1213
|
+
# Loop on all sub-allocation pools
|
1214
|
+
networks = gw.css('Network')
|
1215
|
+
|
1216
|
+
networks.each do |network|
|
1217
|
+
@network_test = true if network['name'] == vdc_network_name
|
1218
|
+
end
|
1219
|
+
end
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
fail Errors::EdgeGWNotConnected,
|
1223
|
+
:edge => edge_gateway_name,
|
1224
|
+
:network => vdc_network_name unless @network_test
|
1225
|
+
|
1226
|
+
@network_test
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
##
|
1230
|
+
# Get Org Edge port forwarding and firewall rules
|
1231
|
+
#
|
1232
|
+
# - vapp_id: id of the vapp to be modified
|
1233
|
+
# - network_name: name of the vapp network to be modified
|
1234
|
+
# - config: hash with network configuration specifications,
|
1235
|
+
# must contain an array inside :nat_rules with the nat rules
|
1236
|
+
# to be applied.
|
1237
|
+
def get_edge_gateway_rules(edge_gateway_name, vdc_id)
|
1238
|
+
edge_gateway_id = find_edge_gateway_id(edge_gateway_name, vdc_id)
|
1239
|
+
|
1240
|
+
params = {
|
1241
|
+
'method' => :get,
|
1242
|
+
'command' => "/admin/edgeGateway/#{edge_gateway_id}"
|
1243
|
+
}
|
1244
|
+
|
1245
|
+
response, _headers = send_request(params)
|
1246
|
+
|
1247
|
+
nat_fw_rules = []
|
1248
|
+
|
1249
|
+
interesting = response.css(
|
1250
|
+
'EdgeGateway Configuration EdgeGatewayServiceConfiguration'
|
1251
|
+
)
|
1252
|
+
interesting.css('NatService NatRule').each do |node|
|
1253
|
+
if node.css('RuleType').text == 'DNAT'
|
1254
|
+
gw_node = node.css('GatewayNatRule')
|
1255
|
+
nat_fw_rules << {
|
1256
|
+
:rule_type => 'DNAT',
|
1257
|
+
:original_ip => gw_node.css('OriginalIp').text,
|
1258
|
+
:original_port => gw_node.css('OriginalPort').text,
|
1259
|
+
:translated_ip => gw_node.css('TranslatedIp').text,
|
1260
|
+
:translated_port => gw_node.css('TranslatedPort').text,
|
1261
|
+
:protocol => gw_node.css('Protocol').text,
|
1262
|
+
:is_enabled => node.css('IsEnabled').text
|
1263
|
+
}
|
1264
|
+
|
1265
|
+
end
|
1266
|
+
if node.css('RuleType').text == 'SNAT'
|
1267
|
+
gw_node = node.css('GatewayNatRule')
|
1268
|
+
nat_fw_rules << {
|
1269
|
+
:rule_type => 'SNAT',
|
1270
|
+
:interface_name => gw_node.css('Interface').first['name'],
|
1271
|
+
:original_ip => gw_node.css('OriginalIp').text,
|
1272
|
+
:translated_ip => gw_node.css('TranslatedIp').text,
|
1273
|
+
:is_enabled => node.css('IsEnabled').text
|
1274
|
+
}
|
1275
|
+
end
|
1276
|
+
end
|
1277
|
+
|
1278
|
+
interesting.css('FirewallService FirewallRule').each do |node|
|
1279
|
+
if node.css('Port').text == '-1'
|
1280
|
+
nat_fw_rules << {
|
1281
|
+
:rule_type => 'Firewall',
|
1282
|
+
:id => node.css('Id').text,
|
1283
|
+
:policy => node.css('Policy').text,
|
1284
|
+
:description => node.css('Description').text,
|
1285
|
+
:destination_ip => node.css('DestinationIp').text,
|
1286
|
+
:destination_portrange => node.css('DestinationPortRange').text,
|
1287
|
+
:source_ip => node.css('SourceIp').text,
|
1288
|
+
:source_portrange => node.css('SourcePortRange').text,
|
1289
|
+
:is_enabled => node.css('IsEnabled').text
|
1290
|
+
}
|
1291
|
+
end
|
1292
|
+
end
|
1293
|
+
|
1294
|
+
nat_fw_rules
|
1295
|
+
end
|
1296
|
+
|
1297
|
+
##
|
1298
|
+
# Remove NAT/FW rules from a edge gateway device
|
1299
|
+
#
|
1300
|
+
# - edge_gateway_name: Name of the vSE
|
1301
|
+
# - vdc_id: virtual datacenter id
|
1302
|
+
# - edge_gateway_ip: public ip associated the vSE
|
1303
|
+
# - vapp_id: vApp identifier to correlate with the vApp Edge
|
1304
|
+
|
1305
|
+
def remove_edge_gateway_rules(edge_gateway_name, vdc_id, edge_gateway_ip, vapp_id)
|
1306
|
+
edge_vapp_ip = get_vapp_edge_public_ip(vapp_id)
|
1307
|
+
edge_gateway_id = find_edge_gateway_id(edge_gateway_name, vdc_id)
|
1308
|
+
|
1309
|
+
params = {
|
1310
|
+
'method' => :get,
|
1311
|
+
'command' => "/admin/edgeGateway/#{edge_gateway_id}"
|
1312
|
+
}
|
1313
|
+
|
1314
|
+
response, _headers = send_request(params)
|
1315
|
+
|
1316
|
+
interesting = response.css(
|
1317
|
+
'EdgeGateway Configuration EdgeGatewayServiceConfiguration'
|
1318
|
+
)
|
1319
|
+
interesting.css('NatService NatRule').each do |node|
|
1320
|
+
if node.css('RuleType').text == 'DNAT' &&
|
1321
|
+
node.css('GatewayNatRule/OriginalIp').text == edge_gateway_ip &&
|
1322
|
+
node.css('GatewayNatRule/TranslatedIp').text == edge_vapp_ip
|
1323
|
+
node.remove
|
1324
|
+
end
|
1325
|
+
if node.css('RuleType').text == 'SNAT' &&
|
1326
|
+
node.css('GatewayNatRule/OriginalIp').text == edge_vapp_ip &&
|
1327
|
+
node.css('GatewayNatRule/TranslatedIp').text == edge_gateway_ip
|
1328
|
+
node.remove
|
1329
|
+
end
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
interesting.css('FirewallService FirewallRule').each do |node|
|
1333
|
+
if node.css('Port').text == '-1' &&
|
1334
|
+
node.css('DestinationIp').text == edge_gateway_ip &&
|
1335
|
+
node.css('DestinationPortRange').text == 'Any'
|
1336
|
+
node.remove
|
1337
|
+
end
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
builder = Nokogiri::XML::Builder.new
|
1341
|
+
builder << interesting
|
1342
|
+
|
1343
|
+
remove_edge_rules = Nokogiri::XML(builder.to_xml)
|
1344
|
+
|
1345
|
+
xml = remove_edge_rules.at_css 'EdgeGatewayServiceConfiguration'
|
1346
|
+
xml['xmlns'] = 'http://www.vmware.com/vcloud/v1.5'
|
1347
|
+
|
1348
|
+
params = {
|
1349
|
+
'method' => :post,
|
1350
|
+
'command' => "/admin/edgeGateway/#{edge_gateway_id}/action/" +
|
1351
|
+
'configureServices'
|
1352
|
+
}
|
1353
|
+
|
1354
|
+
_response, headers = send_request(
|
1355
|
+
params,
|
1356
|
+
remove_edge_rules.to_xml,
|
1357
|
+
'application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml'
|
1358
|
+
)
|
1359
|
+
|
1360
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
1361
|
+
task_id
|
1362
|
+
end
|
1363
|
+
|
1364
|
+
#
|
1365
|
+
# Add Org Edge port forwarding and firewall rules
|
1366
|
+
#
|
1367
|
+
# - vapp_id: id of the vapp to be modified
|
1368
|
+
# - network_name: name of the vapp network to be modified
|
1369
|
+
# - ports: array with port numbers to forward 1:1 to vApp.
|
1370
|
+
def add_edge_gateway_rules(edge_gateway_name, vdc_id, edge_gateway_ip, vapp_id, ports)
|
1371
|
+
edge_vapp_ip = get_vapp_edge_public_ip(vapp_id)
|
1372
|
+
edge_network_id = find_edge_gateway_network(
|
1373
|
+
edge_gateway_name,
|
1374
|
+
vdc_id,
|
1375
|
+
edge_gateway_ip
|
1376
|
+
)
|
1377
|
+
edge_gateway_id = find_edge_gateway_id(edge_gateway_name, vdc_id)
|
1378
|
+
|
1379
|
+
### FIXME: tsugliani
|
1380
|
+
# We need to check the previous variables, especially (edge_*)
|
1381
|
+
# which can fail in some *weird* situations.
|
1382
|
+
params = {
|
1383
|
+
'method' => :get,
|
1384
|
+
'command' => "/admin/edgeGateway/#{edge_gateway_id}"
|
1385
|
+
}
|
1386
|
+
|
1387
|
+
response, _headers = send_request(params)
|
1388
|
+
|
1389
|
+
interesting = response.css(
|
1390
|
+
'EdgeGateway Configuration EdgeGatewayServiceConfiguration'
|
1391
|
+
)
|
1392
|
+
|
1393
|
+
add_snat_rule = true
|
1394
|
+
interesting.css('NatService NatRule').each do |node|
|
1395
|
+
if node.css('RuleType').text == 'DNAT' &&
|
1396
|
+
node.css('GatewayNatRule/OriginalIp').text == edge_gateway_ip &&
|
1397
|
+
node.css('GatewayNatRule/TranslatedIp').text == edge_vapp_ip &&
|
1398
|
+
node.css('GatewayNatRule/OriginalPort').text == 'any'
|
1399
|
+
# remove old DNAT rule any -> any from older vagrant-vcloudair versions
|
1400
|
+
node.remove
|
1401
|
+
end
|
1402
|
+
if node.css('RuleType').text == 'SNAT' &&
|
1403
|
+
node.css('GatewayNatRule/OriginalIp').text == edge_vapp_ip &&
|
1404
|
+
node.css('GatewayNatRule/TranslatedIp').text == edge_gateway_ip
|
1405
|
+
add_snat_rule = false
|
1406
|
+
end
|
1407
|
+
end
|
1408
|
+
|
1409
|
+
add_firewall_rule = true
|
1410
|
+
interesting.css('FirewallService FirewallRule').each do |node|
|
1411
|
+
if node.css('Port').text == '-1' &&
|
1412
|
+
node.css('DestinationIp').text == edge_gateway_ip &&
|
1413
|
+
node.css('DestinationPortRange').text == 'Any'
|
1414
|
+
add_firewall_rule = false
|
1415
|
+
end
|
1416
|
+
end
|
1417
|
+
|
1418
|
+
builder = Nokogiri::XML::Builder.new
|
1419
|
+
builder << interesting
|
1420
|
+
|
1421
|
+
set_edge_rules = Nokogiri::XML(builder.to_xml) do |config|
|
1422
|
+
config.default_xml.noblanks
|
1423
|
+
end
|
1424
|
+
|
1425
|
+
nat_rules = set_edge_rules.at_css('NatService')
|
1426
|
+
|
1427
|
+
# Add all DNAT port rules edge -> vApp for the given list
|
1428
|
+
ports.each do |port|
|
1429
|
+
nat_rule = Nokogiri::XML::Builder.new do |xml|
|
1430
|
+
xml.NatRule {
|
1431
|
+
xml.RuleType 'DNAT'
|
1432
|
+
xml.IsEnabled 'true'
|
1433
|
+
xml.GatewayNatRule {
|
1434
|
+
xml.Interface('href' => edge_network_id )
|
1435
|
+
xml.OriginalIp edge_gateway_ip
|
1436
|
+
xml.OriginalPort port
|
1437
|
+
xml.TranslatedIp edge_vapp_ip
|
1438
|
+
xml.TranslatedPort port
|
1439
|
+
xml.Protocol 'tcpudp'
|
1440
|
+
}
|
1441
|
+
}
|
1442
|
+
end
|
1443
|
+
nat_rules << nat_rule.doc.root.to_xml
|
1444
|
+
end
|
1445
|
+
|
1446
|
+
if (add_snat_rule)
|
1447
|
+
snat_rule = Nokogiri::XML::Builder.new do |xml|
|
1448
|
+
xml.NatRule {
|
1449
|
+
xml.RuleType 'SNAT'
|
1450
|
+
xml.IsEnabled 'true'
|
1451
|
+
xml.GatewayNatRule {
|
1452
|
+
xml.Interface('href' => edge_network_id )
|
1453
|
+
xml.OriginalIp edge_vapp_ip
|
1454
|
+
xml.TranslatedIp edge_gateway_ip
|
1455
|
+
xml.Protocol 'any'
|
1456
|
+
}
|
1457
|
+
}
|
1458
|
+
end
|
1459
|
+
nat_rules << snat_rule.doc.root.to_xml
|
1460
|
+
end
|
1461
|
+
|
1462
|
+
|
1463
|
+
if (add_firewall_rule)
|
1464
|
+
firewall_rule_1 = Nokogiri::XML::Builder.new do |xml|
|
1465
|
+
xml.FirewallRule {
|
1466
|
+
xml.IsEnabled 'true'
|
1467
|
+
xml.Description 'Allow Vagrant Communications'
|
1468
|
+
xml.Policy 'allow'
|
1469
|
+
xml.Protocols {
|
1470
|
+
xml.Any 'true'
|
1471
|
+
}
|
1472
|
+
xml.DestinationPortRange 'Any'
|
1473
|
+
xml.DestinationIp edge_gateway_ip
|
1474
|
+
xml.SourcePortRange 'Any'
|
1475
|
+
xml.SourceIp 'Any'
|
1476
|
+
xml.EnableLogging 'false'
|
1477
|
+
}
|
1478
|
+
end
|
1479
|
+
fw_rules = set_edge_rules.at_css('FirewallService')
|
1480
|
+
fw_rules << firewall_rule_1.doc.root.to_xml
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
xml = set_edge_rules.at_css 'EdgeGatewayServiceConfiguration'
|
1484
|
+
xml['xmlns'] = 'http://www.vmware.com/vcloud/v1.5'
|
1485
|
+
|
1486
|
+
params = {
|
1487
|
+
'method' => :post,
|
1488
|
+
'command' => "/admin/edgeGateway/#{edge_gateway_id}/action/" +
|
1489
|
+
'configureServices'
|
1490
|
+
}
|
1491
|
+
|
1492
|
+
_response, headers = send_request(
|
1493
|
+
params,
|
1494
|
+
set_edge_rules.to_xml,
|
1495
|
+
'application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml'
|
1496
|
+
)
|
1497
|
+
|
1498
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
1499
|
+
task_id
|
1500
|
+
end
|
1501
|
+
|
1502
|
+
|
1503
|
+
##
|
1504
|
+
# get vApp edge public IP from the vApp ID
|
1505
|
+
# Only works when:
|
1506
|
+
# - vApp needs to be poweredOn
|
1507
|
+
# - FenceMode is set to "natRouted"
|
1508
|
+
# - NatType" is set to "portForwarding
|
1509
|
+
# This will be required to know how to connect to VMs behind the Edge
|
1510
|
+
# device.
|
1511
|
+
def get_vapp_edge_public_ip(vapp_id)
|
1512
|
+
return @cached_vapp_edge_public_ips[vapp_id] unless @cached_vapp_edge_public_ips[vapp_id].nil?
|
1513
|
+
|
1514
|
+
# Check the network configuration section
|
1515
|
+
params = {
|
1516
|
+
'method' => :get,
|
1517
|
+
'command' => "/vApp/vapp-#{vapp_id}/networkConfigSection"
|
1518
|
+
}
|
1519
|
+
|
1520
|
+
response, _headers = send_request(params)
|
1521
|
+
|
1522
|
+
# FIXME: this will return nil if the vApp uses multiple vApp Networks
|
1523
|
+
# with Edge devices in natRouted/portForwarding mode.
|
1524
|
+
config = response.css(
|
1525
|
+
'NetworkConfigSection/NetworkConfig/Configuration'
|
1526
|
+
)
|
1527
|
+
|
1528
|
+
fence_mode = config.css('/FenceMode').text
|
1529
|
+
nat_type = config.css('/Features/NatService/NatType').text
|
1530
|
+
|
1531
|
+
unless fence_mode == 'natRouted'
|
1532
|
+
fail Errors::InvalidStateError,
|
1533
|
+
'Invalid request because FenceMode must be natRouted.'
|
1534
|
+
end
|
1535
|
+
|
1536
|
+
unless nat_type == 'portForwarding'
|
1537
|
+
fail Errors::InvalidStateError,
|
1538
|
+
'Invalid request because NatType must be portForwarding.'
|
1539
|
+
end
|
1540
|
+
|
1541
|
+
# Check the routerInfo configuration where the global external IP
|
1542
|
+
# is defined
|
1543
|
+
edge_ip = config.css('/RouterInfo/ExternalIp').text
|
1544
|
+
if edge_ip == ''
|
1545
|
+
return nil
|
1546
|
+
else
|
1547
|
+
@cached_vapp_edge_public_ips[vapp_id] = edge_ip
|
1548
|
+
return edge_ip
|
1549
|
+
end
|
1550
|
+
end
|
1551
|
+
|
1552
|
+
##
|
1553
|
+
# Upload an OVF package
|
1554
|
+
# - vdc_id
|
1555
|
+
# - vappName
|
1556
|
+
# - vappDescription
|
1557
|
+
# - ovfFile
|
1558
|
+
# - catalogId
|
1559
|
+
# - uploadOptions {}
|
1560
|
+
def upload_ovf(vdc_id, vapp_name, vapp_description, ovf_file, catalog_id, upload_options = {})
|
1561
|
+
# if send_manifest is not set, setting it true
|
1562
|
+
if upload_options[:send_manifest].nil? ||
|
1563
|
+
upload_options[:send_manifest]
|
1564
|
+
upload_manifest = 'true'
|
1565
|
+
else
|
1566
|
+
upload_manifest = 'false'
|
1567
|
+
end
|
1568
|
+
|
1569
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
1570
|
+
xml.UploadVAppTemplateParams(
|
1571
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5',
|
1572
|
+
'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1',
|
1573
|
+
'manifestRequired' => upload_manifest,
|
1574
|
+
'name' => vapp_name) {
|
1575
|
+
xml.Description vapp_description
|
1576
|
+
}
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
params = {
|
1580
|
+
'method' => :post,
|
1581
|
+
'command' => "/vdc/#{vdc_id}/action/uploadVAppTemplate"
|
1582
|
+
}
|
1583
|
+
|
1584
|
+
@logger.debug('Sending uploadVAppTemplate request...')
|
1585
|
+
|
1586
|
+
response, headers = send_request(
|
1587
|
+
params,
|
1588
|
+
builder.to_xml,
|
1589
|
+
'application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml'
|
1590
|
+
)
|
1591
|
+
|
1592
|
+
# Get vAppTemplate Link from location
|
1593
|
+
vapp_template = URI(headers['Location']).path.gsub(
|
1594
|
+
'/api/vAppTemplate/vappTemplate-', ''
|
1595
|
+
)
|
1596
|
+
|
1597
|
+
@logger.debug("Getting vAppTemplate ID: #{vapp_template}")
|
1598
|
+
descriptor_upload = URI(response.css(
|
1599
|
+
"Files Link [rel='upload:default']"
|
1600
|
+
).first[:href]).path.gsub('/transfer/', '')
|
1601
|
+
transfer_guid = descriptor_upload.gsub('/descriptor.ovf', '')
|
1602
|
+
|
1603
|
+
ovf_file_basename = File.basename(ovf_file, '.ovf')
|
1604
|
+
ovf_dir = File.dirname(ovf_file)
|
1605
|
+
|
1606
|
+
# Send OVF Descriptor
|
1607
|
+
@logger.debug('Sending OVF Descriptor...')
|
1608
|
+
upload_url = "/transfer/#{descriptor_upload}"
|
1609
|
+
upload_filename = "#{ovf_dir}/#{ovf_file_basename}.ovf"
|
1610
|
+
upload_file(
|
1611
|
+
upload_url,
|
1612
|
+
upload_filename,
|
1613
|
+
vapp_template,
|
1614
|
+
upload_options
|
1615
|
+
)
|
1616
|
+
|
1617
|
+
# Begin the catch for upload interruption
|
1618
|
+
begin
|
1619
|
+
params = {
|
1620
|
+
'method' => :get,
|
1621
|
+
'command' => "/vAppTemplate/vappTemplate-#{vapp_template}"
|
1622
|
+
}
|
1623
|
+
|
1624
|
+
response, _headers = send_request(params)
|
1625
|
+
|
1626
|
+
task = response.css(
|
1627
|
+
"VAppTemplate Task[operationName='vdcUploadOvfContents']"
|
1628
|
+
).first
|
1629
|
+
task_id = URI(task['href']).path.gsub('/api/task/', '')
|
1630
|
+
|
1631
|
+
# Loop to wait for the upload links to show up in the vAppTemplate
|
1632
|
+
# we just created
|
1633
|
+
@logger.debug(
|
1634
|
+
'Waiting for the upload links to show up in the vAppTemplate ' \
|
1635
|
+
'we just created.'
|
1636
|
+
)
|
1637
|
+
while true
|
1638
|
+
response, _headers = send_request(params)
|
1639
|
+
@logger.debug('Request...')
|
1640
|
+
break unless response.css("Files Link [rel='upload:default']").count == 1
|
1641
|
+
sleep 1
|
1642
|
+
end
|
1643
|
+
|
1644
|
+
if upload_manifest == 'true'
|
1645
|
+
upload_url = "/transfer/#{transfer_guid}/descriptor.mf"
|
1646
|
+
upload_filename = "#{ovf_dir}/#{ovf_file_basename}.mf"
|
1647
|
+
upload_file(
|
1648
|
+
upload_url,
|
1649
|
+
upload_filename,
|
1650
|
+
vapp_template,
|
1651
|
+
upload_options
|
1652
|
+
)
|
1653
|
+
end
|
1654
|
+
|
1655
|
+
# Start uploading OVF VMDK files
|
1656
|
+
params = {
|
1657
|
+
'method' => :get,
|
1658
|
+
'command' => "/vAppTemplate/vappTemplate-#{vapp_template}"
|
1659
|
+
}
|
1660
|
+
response, _headers = send_request(params)
|
1661
|
+
response.css(
|
1662
|
+
"Files File [bytesTransferred='0'] Link [rel='upload:default']"
|
1663
|
+
).each do |file|
|
1664
|
+
file_name = URI(file[:href]).path.gsub("/transfer/#{transfer_guid}/", '')
|
1665
|
+
upload_filename = "#{ovf_dir}/#{file_name}"
|
1666
|
+
upload_url = "/transfer/#{transfer_guid}/#{file_name}"
|
1667
|
+
upload_file(
|
1668
|
+
upload_url,
|
1669
|
+
upload_filename,
|
1670
|
+
vapp_template,
|
1671
|
+
upload_options
|
1672
|
+
)
|
1673
|
+
end
|
1674
|
+
|
1675
|
+
# Add item to the catalog catalog_id
|
1676
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
1677
|
+
xml.CatalogItem(
|
1678
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5',
|
1679
|
+
'type' => 'application/vnd.vmware.vcloud.catalogItem+xml',
|
1680
|
+
'name' => vapp_name) {
|
1681
|
+
xml.Description vapp_description
|
1682
|
+
xml.Entity(
|
1683
|
+
'href' => "#{@api_url}/vAppTemplate/" +
|
1684
|
+
"vappTemplate-#{vapp_template}"
|
1685
|
+
)
|
1686
|
+
}
|
1687
|
+
end
|
1688
|
+
|
1689
|
+
params = {
|
1690
|
+
'method' => :post,
|
1691
|
+
'command' => "/catalog/#{catalog_id}/catalogItems"
|
1692
|
+
}
|
1693
|
+
# No debug here (tsugliani)
|
1694
|
+
_response, _headers = send_request(
|
1695
|
+
params,
|
1696
|
+
builder.to_xml,
|
1697
|
+
'application/vnd.vmware.vcloud.catalogItem+xml'
|
1698
|
+
)
|
1699
|
+
|
1700
|
+
task_id
|
1701
|
+
|
1702
|
+
######
|
1703
|
+
|
1704
|
+
rescue Exception => e
|
1705
|
+
puts "Exception detected: #{e.message}."
|
1706
|
+
puts 'Aborting task...'
|
1707
|
+
|
1708
|
+
# Get vAppTemplate Task
|
1709
|
+
params = {
|
1710
|
+
'method' => :get,
|
1711
|
+
'command' => "/vAppTemplate/vappTemplate-#{vapp_template}"
|
1712
|
+
}
|
1713
|
+
response, _headers = send_request(params)
|
1714
|
+
|
1715
|
+
# Cancel Task
|
1716
|
+
cancel_hook = URI(response.css(
|
1717
|
+
"Tasks Task Link [rel='task:cancel']"
|
1718
|
+
).first[:href]).path.gsub('/api', '')
|
1719
|
+
|
1720
|
+
params = {
|
1721
|
+
'method' => :post,
|
1722
|
+
'command' => cancel_hook
|
1723
|
+
}
|
1724
|
+
# FIXME: No debug here (tsugliani)
|
1725
|
+
_response, _headers = send_request(params)
|
1726
|
+
raise
|
1727
|
+
end
|
1728
|
+
end
|
1729
|
+
|
1730
|
+
##
|
1731
|
+
# Fetch information for a given task
|
1732
|
+
def get_task(task_id)
|
1733
|
+
params = {
|
1734
|
+
'method' => :get,
|
1735
|
+
'command' => "/task/#{task_id}"
|
1736
|
+
}
|
1737
|
+
|
1738
|
+
response, _headers = send_request(params)
|
1739
|
+
|
1740
|
+
task = response.css('Task').first
|
1741
|
+
status = task['status']
|
1742
|
+
start_time = task['startTime']
|
1743
|
+
end_time = task['endTime']
|
1744
|
+
|
1745
|
+
{
|
1746
|
+
:status => status,
|
1747
|
+
:start_time => start_time,
|
1748
|
+
:end_time => end_time,
|
1749
|
+
:response => response
|
1750
|
+
}
|
1751
|
+
end
|
1752
|
+
|
1753
|
+
##
|
1754
|
+
# Poll a given task until completion
|
1755
|
+
def wait_task_completion(task_id)
|
1756
|
+
task, errormsg = nil
|
1757
|
+
loop do
|
1758
|
+
task = get_task(task_id)
|
1759
|
+
# @logger.debug(
|
1760
|
+
# "Evaluating taskid: #{task_id}, current status #{task[:status]}"
|
1761
|
+
# )
|
1762
|
+
break if !['queued','preRunning','running'].include?(task[:status])
|
1763
|
+
sleep 5
|
1764
|
+
end
|
1765
|
+
|
1766
|
+
if task[:status] == 'error'
|
1767
|
+
@logger.debug('Task Error')
|
1768
|
+
errormsg = task[:response].css('Error').first
|
1769
|
+
@logger.debug(
|
1770
|
+
"Task Error Message #{errormsg['majorErrorCode']} - " +
|
1771
|
+
"#{errormsg['message']}"
|
1772
|
+
)
|
1773
|
+
errormsg =
|
1774
|
+
"Error code #{errormsg['majorErrorCode']} - #{errormsg['message']}"
|
1775
|
+
end
|
1776
|
+
|
1777
|
+
{
|
1778
|
+
:status => task[:status],
|
1779
|
+
:errormsg => errormsg,
|
1780
|
+
:start_time => task[:start_time],
|
1781
|
+
:end_time => task[:end_time]
|
1782
|
+
}
|
1783
|
+
end
|
1784
|
+
|
1785
|
+
##
|
1786
|
+
# Set vApp Network Config
|
1787
|
+
def set_vapp_network_config(vapp_id, network_name, config = {})
|
1788
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
1789
|
+
xml.NetworkConfigSection(
|
1790
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5',
|
1791
|
+
'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1'
|
1792
|
+
) {
|
1793
|
+
xml['ovf'].Info 'Network configuration'
|
1794
|
+
xml.NetworkConfig('networkName' => network_name) {
|
1795
|
+
xml.Configuration {
|
1796
|
+
xml.FenceMode(config[:fence_mode] || 'isolated')
|
1797
|
+
xml.RetainNetInfoAcrossDeployments(config[:retain_net] || false)
|
1798
|
+
xml.ParentNetwork('href' => config[:parent_network])
|
1799
|
+
}
|
1800
|
+
}
|
1801
|
+
}
|
1802
|
+
end
|
1803
|
+
|
1804
|
+
params = {
|
1805
|
+
'method' => :put,
|
1806
|
+
'command' => "/vApp/vapp-#{vapp_id}/networkConfigSection"
|
1807
|
+
}
|
1808
|
+
|
1809
|
+
_response, headers = send_request(
|
1810
|
+
params,
|
1811
|
+
builder.to_xml,
|
1812
|
+
'application/vnd.vmware.vcloud.networkConfigSection+xml'
|
1813
|
+
)
|
1814
|
+
|
1815
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
1816
|
+
task_id
|
1817
|
+
end
|
1818
|
+
|
1819
|
+
##
|
1820
|
+
# Set VM Network Config
|
1821
|
+
def set_vm_network_config(vm_id, network_name, config = {})
|
1822
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
1823
|
+
xml.NetworkConnectionSection(
|
1824
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5',
|
1825
|
+
'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1') {
|
1826
|
+
xml['ovf'].Info 'VM Network configuration'
|
1827
|
+
xml.PrimaryNetworkConnectionIndex(config[:primary_index] || 0)
|
1828
|
+
xml.NetworkConnection(
|
1829
|
+
'network' => network_name,
|
1830
|
+
'needsCustomization' => true
|
1831
|
+
) {
|
1832
|
+
xml.NetworkConnectionIndex(config[:network_index] || 0)
|
1833
|
+
xml.IpAddress config[:ip] if config[:ip]
|
1834
|
+
xml.IsConnected(config[:is_connected] || true)
|
1835
|
+
xml.IpAddressAllocationMode config[:ip_allocation_mode] if config[:ip_allocation_mode]
|
1836
|
+
}
|
1837
|
+
}
|
1838
|
+
end
|
1839
|
+
|
1840
|
+
params = {
|
1841
|
+
'method' => :put,
|
1842
|
+
'command' => "/vApp/vm-#{vm_id}/networkConnectionSection"
|
1843
|
+
}
|
1844
|
+
|
1845
|
+
_response, headers = send_request(
|
1846
|
+
params,
|
1847
|
+
builder.to_xml,
|
1848
|
+
'application/vnd.vmware.vcloud.networkConnectionSection+xml'
|
1849
|
+
)
|
1850
|
+
|
1851
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
1852
|
+
task_id
|
1853
|
+
end
|
1854
|
+
|
1855
|
+
##
|
1856
|
+
# Set VM Guest Customization Config
|
1857
|
+
def set_vm_guest_customization(vm_id, computer_name, config = {})
|
1858
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
1859
|
+
xml.GuestCustomizationSection(
|
1860
|
+
'xmlns' => 'http://www.vmware.com/vcloud/v1.5',
|
1861
|
+
'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1') {
|
1862
|
+
xml['ovf'].Info 'VM Guest Customization configuration'
|
1863
|
+
xml.Enabled config[:enabled] if config[:enabled]
|
1864
|
+
xml.AdminPasswordEnabled config[:admin_passwd_enabled] if config[:admin_passwd_enabled]
|
1865
|
+
xml.AdminPassword config[:admin_passwd] if config[:admin_passwd]
|
1866
|
+
xml.ComputerName computer_name
|
1867
|
+
}
|
1868
|
+
end
|
1869
|
+
|
1870
|
+
params = {
|
1871
|
+
'method' => :put,
|
1872
|
+
'command' => "/vApp/vm-#{vm_id}/guestCustomizationSection"
|
1873
|
+
}
|
1874
|
+
|
1875
|
+
_response, headers = send_request(
|
1876
|
+
params,
|
1877
|
+
builder.to_xml,
|
1878
|
+
'application/vnd.vmware.vcloud.guestCustomizationSection+xml'
|
1879
|
+
)
|
1880
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
1881
|
+
task_id
|
1882
|
+
end
|
1883
|
+
|
1884
|
+
# Enable VM Nested Hardware-Assisted Virtualization
|
1885
|
+
def set_vm_nested_hypervisor(vm_id, enable)
|
1886
|
+
action = enable ? "enable" : "disable"
|
1887
|
+
params = {
|
1888
|
+
'method' => :post,
|
1889
|
+
'command' => "/vApp/vm-#{vm_id}/action/#{action}NestedHypervisor"
|
1890
|
+
}
|
1891
|
+
|
1892
|
+
_response, headers = send_request(params)
|
1893
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
1894
|
+
task_id
|
1895
|
+
end
|
1896
|
+
|
1897
|
+
##
|
1898
|
+
# Set memory and number of cpus in virtualHardwareSection of a given vm
|
1899
|
+
# returns task_id or nil if there is no task to wait for
|
1900
|
+
def set_vm_hardware(vm_id, cfg)
|
1901
|
+
params = {
|
1902
|
+
'method' => :get,
|
1903
|
+
'command' => "/vApp/vm-#{vm_id}/virtualHardwareSection"
|
1904
|
+
}
|
1905
|
+
|
1906
|
+
changed = false
|
1907
|
+
response, _headers = send_request(params)
|
1908
|
+
|
1909
|
+
response.css('ovf|Item').each do |item|
|
1910
|
+
type = item.css('rasd|ResourceType').first
|
1911
|
+
if type.content == '3'
|
1912
|
+
# cpus
|
1913
|
+
if cfg.cpus
|
1914
|
+
if item.at_css('rasd|VirtualQuantity').content != cfg.cpus.to_s
|
1915
|
+
item.at_css('rasd|VirtualQuantity').content = cfg.cpus
|
1916
|
+
item.at_css('rasd|ElementName').content = "#{cfg.cpus} virtual CPU(s)"
|
1917
|
+
changed = true
|
1918
|
+
end
|
1919
|
+
end
|
1920
|
+
elsif type.content == '4'
|
1921
|
+
# memory
|
1922
|
+
if cfg.memory
|
1923
|
+
if item.at_css('rasd|VirtualQuantity').content != cfg.memory.to_s
|
1924
|
+
item.at_css('rasd|VirtualQuantity').content = cfg.memory
|
1925
|
+
item.at_css('rasd|ElementName').content = "#{cfg.memory} MB of memory"
|
1926
|
+
changed = true
|
1927
|
+
end
|
1928
|
+
end
|
1929
|
+
end
|
1930
|
+
end
|
1931
|
+
|
1932
|
+
if changed
|
1933
|
+
params = {
|
1934
|
+
'method' => :put,
|
1935
|
+
'command' => "/vApp/vm-#{vm_id}/virtualHardwareSection"
|
1936
|
+
}
|
1937
|
+
|
1938
|
+
_response, headers = send_request(
|
1939
|
+
params,
|
1940
|
+
response.to_xml,
|
1941
|
+
'application/vnd.vmware.vcloud.virtualhardwaresection+xml'
|
1942
|
+
)
|
1943
|
+
|
1944
|
+
task_id = URI(headers['Location']).path.gsub('/api/task/', '')
|
1945
|
+
task_id
|
1946
|
+
else
|
1947
|
+
return nil
|
1948
|
+
end
|
1949
|
+
end
|
1950
|
+
|
1951
|
+
|
1952
|
+
##
|
1953
|
+
# Fetch details about a given VM
|
1954
|
+
def get_vm(vm_id)
|
1955
|
+
params = {
|
1956
|
+
'method' => :get,
|
1957
|
+
'command' => "/vApp/vm-#{vm_id}"
|
1958
|
+
}
|
1959
|
+
|
1960
|
+
response, _headers = send_request(params)
|
1961
|
+
|
1962
|
+
os_desc = response.css(
|
1963
|
+
'ovf|OperatingSystemSection ovf|Description'
|
1964
|
+
).first.text
|
1965
|
+
|
1966
|
+
networks = {}
|
1967
|
+
response.css('NetworkConnection').each do |network|
|
1968
|
+
ip = network.css('IpAddress').first
|
1969
|
+
ip = ip.text if ip
|
1970
|
+
|
1971
|
+
networks[network['network']] = {
|
1972
|
+
:index => network.css(
|
1973
|
+
'NetworkConnectionIndex'
|
1974
|
+
).first.text,
|
1975
|
+
:ip => ip,
|
1976
|
+
:is_connected => network.css(
|
1977
|
+
'IsConnected'
|
1978
|
+
).first.text,
|
1979
|
+
:mac_address => network.css(
|
1980
|
+
'MACAddress'
|
1981
|
+
).first.text,
|
1982
|
+
:ip_allocation_mode => network.css(
|
1983
|
+
'IpAddressAllocationMode'
|
1984
|
+
).first.text
|
1985
|
+
}
|
1986
|
+
end
|
1987
|
+
|
1988
|
+
admin_password = response.css(
|
1989
|
+
'GuestCustomizationSection AdminPassword'
|
1990
|
+
).first
|
1991
|
+
admin_password = admin_password.text if admin_password
|
1992
|
+
|
1993
|
+
# make the lines shorter by adjusting the nokogiri css namespace
|
1994
|
+
guest_css = response.css('GuestCustomizationSection')
|
1995
|
+
guest_customizations = {
|
1996
|
+
:enabled => guest_css.css('Enabled').first.text,
|
1997
|
+
:admin_passwd_enabled => guest_css.css(
|
1998
|
+
'AdminPasswordEnabled'
|
1999
|
+
).first.text,
|
2000
|
+
:admin_passwd_auto => guest_css.css(
|
2001
|
+
'AdminPasswordAuto'
|
2002
|
+
).first.text,
|
2003
|
+
:admin_passwd => admin_password,
|
2004
|
+
:reset_passwd_required => guest_css.css(
|
2005
|
+
'ResetPasswordRequired'
|
2006
|
+
).first.text,
|
2007
|
+
:computer_name => guest_css.css('ComputerName').first.text
|
2008
|
+
}
|
2009
|
+
|
2010
|
+
{
|
2011
|
+
:os_desc => os_desc,
|
2012
|
+
:networks => networks,
|
2013
|
+
:guest_customizations => guest_customizations
|
2014
|
+
}
|
2015
|
+
end
|
2016
|
+
end # Class Version 5.1
|
2017
|
+
end # Module Driver
|
2018
|
+
end # module VCloudAir
|
2019
|
+
end # Module VagrantPlugins
|