vcloud-rest 0.3.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +60 -0
- data/README.md +21 -9
- data/lib/vcloud-rest/connection.rb +72 -890
- data/lib/vcloud-rest/vcloud/catalog.rb +113 -0
- data/lib/vcloud-rest/vcloud/network.rb +78 -0
- data/lib/vcloud-rest/vcloud/org.rb +145 -0
- data/lib/vcloud-rest/vcloud/ovf.rb +251 -0
- data/lib/vcloud-rest/vcloud/vapp.rb +428 -0
- data/lib/vcloud-rest/vcloud/vapp_networking.rb +331 -0
- data/lib/vcloud-rest/vcloud/vdc.rb +67 -0
- data/lib/vcloud-rest/vcloud/vm.rb +396 -0
- data/lib/vcloud-rest/version.rb +3 -0
- metadata +18 -9
@@ -0,0 +1,331 @@
|
|
1
|
+
module VCloudClient
|
2
|
+
class Connection
|
3
|
+
##
|
4
|
+
# Set vApp Network Config
|
5
|
+
#
|
6
|
+
# Retrieve the existing network config section and edit it
|
7
|
+
# to ensure settings are not lost
|
8
|
+
def set_vapp_network_config(vappid, network, config={})
|
9
|
+
params = {
|
10
|
+
'method' => :get,
|
11
|
+
'command' => "/vApp/vapp-#{vappid}/networkConfigSection"
|
12
|
+
}
|
13
|
+
|
14
|
+
netconfig_response, headers = send_request(params)
|
15
|
+
|
16
|
+
picked_network = netconfig_response.css("NetworkConfig").select do |net|
|
17
|
+
net.attribute('networkName').text == network[:name]
|
18
|
+
end.first
|
19
|
+
|
20
|
+
raise WrongItemIDError, "Network named #{network[:name]} not found." unless picked_network
|
21
|
+
|
22
|
+
picked_network.css('FenceMode').first.content = config[:fence_mode] if config[:fence_mode]
|
23
|
+
picked_network.css('IsInherited').first.content = "true"
|
24
|
+
picked_network.css('RetainNetInfoAcrossDeployments').first.content = config[:retain_network] if config[:retain_network]
|
25
|
+
|
26
|
+
if config[:parent_network]
|
27
|
+
parent_network = picked_network.css('ParentNetwork').first
|
28
|
+
new_parent = false
|
29
|
+
|
30
|
+
unless parent_network
|
31
|
+
new_parent = true
|
32
|
+
ipscopes = picked_network.css('IpScopes').first
|
33
|
+
parent_network = Nokogiri::XML::Node.new "ParentNetwork", ipscopes.parent
|
34
|
+
end
|
35
|
+
|
36
|
+
parent_network["name"] = "#{config[:parent_network][:name]}"
|
37
|
+
parent_network["id"] = "#{config[:parent_network][:id]}"
|
38
|
+
parent_network["href"] = "#{@api_url}/admin/network/#{config[:parent_network][:id]}"
|
39
|
+
ipscopes.add_next_sibling(parent_network) if new_parent
|
40
|
+
end
|
41
|
+
|
42
|
+
data = netconfig_response.to_xml
|
43
|
+
|
44
|
+
params = {
|
45
|
+
'method' => :put,
|
46
|
+
'command' => "/vApp/vapp-#{vappid}/networkConfigSection"
|
47
|
+
}
|
48
|
+
|
49
|
+
response, headers = send_request(params, data, "application/vnd.vmware.vcloud.networkConfigSection+xml")
|
50
|
+
|
51
|
+
task_id = headers[:location].gsub(/.*\/task\//, "")
|
52
|
+
task_id
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
##
|
57
|
+
# Add an existing network (from Org) to vApp
|
58
|
+
#
|
59
|
+
#
|
60
|
+
def add_org_network_to_vapp(vAppId, network, config)
|
61
|
+
network_section = generate_network_section(vAppId, network, config, :external)
|
62
|
+
add_network_to_vapp(vAppId, network_section)
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Add an existing network (from Org) to vApp
|
67
|
+
def add_internal_network_to_vapp(vAppId, network, config)
|
68
|
+
network_section = generate_network_section(vAppId, network, config, :internal)
|
69
|
+
add_network_to_vapp(vAppId, network_section)
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Remove an existing network
|
74
|
+
def delete_vapp_network(vAppId, network)
|
75
|
+
params = {
|
76
|
+
'method' => :get,
|
77
|
+
'command' => "/vApp/vapp-#{vAppId}/networkConfigSection"
|
78
|
+
}
|
79
|
+
|
80
|
+
netconfig_response, headers = send_request(params)
|
81
|
+
|
82
|
+
picked_network = netconfig_response.css("NetworkConfig").select do |net|
|
83
|
+
net.attribute('networkName').text == network[:name]
|
84
|
+
end.first
|
85
|
+
|
86
|
+
raise WrongItemIDError, "Network #{network[:name]} not found on this vApp." unless picked_network
|
87
|
+
|
88
|
+
picked_network.remove
|
89
|
+
|
90
|
+
params = {
|
91
|
+
'method' => :put,
|
92
|
+
'command' => "/vApp/vapp-#{vAppId}/networkConfigSection"
|
93
|
+
}
|
94
|
+
|
95
|
+
put_response, headers = send_request(params, netconfig_response.to_xml, "application/vnd.vmware.vcloud.networkConfigSection+xml")
|
96
|
+
|
97
|
+
task_id = headers[:location].gsub(/.*\/task\//, "")
|
98
|
+
task_id
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Set vApp port forwarding rules
|
103
|
+
#
|
104
|
+
# - vappid: id of the vapp to be modified
|
105
|
+
# - network_name: name of the vapp network to be modified
|
106
|
+
# - config: hash with network configuration specifications, must contain an array inside :nat_rules with the nat rules to be applied.
|
107
|
+
def set_vapp_port_forwarding_rules(vappid, network_name, config={})
|
108
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
109
|
+
xml.NetworkConfigSection(
|
110
|
+
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
111
|
+
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1") {
|
112
|
+
xml['ovf'].Info "Network configuration"
|
113
|
+
xml.NetworkConfig("networkName" => network_name) {
|
114
|
+
xml.Configuration {
|
115
|
+
xml.ParentNetwork("href" => "#{@api_url}/network/#{config[:parent_network]}")
|
116
|
+
xml.FenceMode(config[:fence_mode] || 'isolated')
|
117
|
+
xml.Features {
|
118
|
+
xml.NatService {
|
119
|
+
xml.IsEnabled "true"
|
120
|
+
xml.NatType "portForwarding"
|
121
|
+
xml.Policy(config[:nat_policy_type] || "allowTraffic")
|
122
|
+
config[:nat_rules].each do |nat_rule|
|
123
|
+
xml.NatRule {
|
124
|
+
xml.VmRule {
|
125
|
+
xml.ExternalPort nat_rule[:nat_external_port]
|
126
|
+
xml.VAppScopedVmId nat_rule[:vm_scoped_local_id]
|
127
|
+
xml.VmNicId(nat_rule[:nat_vmnic_id] || "0")
|
128
|
+
xml.InternalPort nat_rule[:nat_internal_port]
|
129
|
+
xml.Protocol(nat_rule[:nat_protocol] || "TCP")
|
130
|
+
}
|
131
|
+
}
|
132
|
+
end
|
133
|
+
}
|
134
|
+
}
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
params = {
|
141
|
+
'method' => :put,
|
142
|
+
'command' => "/vApp/vapp-#{vappid}/networkConfigSection"
|
143
|
+
}
|
144
|
+
|
145
|
+
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.networkConfigSection+xml")
|
146
|
+
|
147
|
+
task_id = headers[:location].gsub(/.*\/task\//, "")
|
148
|
+
task_id
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Get vApp port forwarding rules
|
153
|
+
#
|
154
|
+
# - vappid: id of the vApp
|
155
|
+
def get_vapp_port_forwarding_rules(vAppId)
|
156
|
+
params = {
|
157
|
+
'method' => :get,
|
158
|
+
'command' => "/vApp/vapp-#{vAppId}/networkConfigSection"
|
159
|
+
}
|
160
|
+
|
161
|
+
response, headers = send_request(params)
|
162
|
+
|
163
|
+
# FIXME: this will return nil if the vApp uses multiple vApp Networks
|
164
|
+
# with Edge devices in natRouted/portForwarding mode.
|
165
|
+
config = response.css('NetworkConfigSection/NetworkConfig/Configuration')
|
166
|
+
fenceMode = config.css('/FenceMode').text
|
167
|
+
natType = config.css('/Features/NatService/NatType').text
|
168
|
+
|
169
|
+
raise InvalidStateError, "Invalid request because FenceMode must be set to natRouted." unless fenceMode == "natRouted"
|
170
|
+
raise InvalidStateError, "Invalid request because NatType must be set to portForwarding." unless natType == "portForwarding"
|
171
|
+
|
172
|
+
nat_rules = {}
|
173
|
+
config.css('/Features/NatService/NatRule').each do |rule|
|
174
|
+
# portforwarding rules information
|
175
|
+
ruleId = rule.css('Id').text
|
176
|
+
vmRule = rule.css('VmRule')
|
177
|
+
|
178
|
+
nat_rules[rule.css('Id').text] = {
|
179
|
+
:ExternalIpAddress => vmRule.css('ExternalIpAddress').text,
|
180
|
+
:ExternalPort => vmRule.css('ExternalPort').text,
|
181
|
+
:VAppScopedVmId => vmRule.css('VAppScopedVmId').text,
|
182
|
+
:VmNicId => vmRule.css('VmNicId').text,
|
183
|
+
:InternalPort => vmRule.css('InternalPort').text,
|
184
|
+
:Protocol => vmRule.css('Protocol').text
|
185
|
+
}
|
186
|
+
end
|
187
|
+
nat_rules
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# get vApp edge public IP from the vApp ID
|
192
|
+
# Only works when:
|
193
|
+
# - vApp needs to be poweredOn
|
194
|
+
# - FenceMode is set to "natRouted"
|
195
|
+
# - NatType" is set to "portForwarding
|
196
|
+
# This will be required to know how to connect to VMs behind the Edge device.
|
197
|
+
def get_vapp_edge_public_ip(vAppId)
|
198
|
+
# Check the network configuration section
|
199
|
+
params = {
|
200
|
+
'method' => :get,
|
201
|
+
'command' => "/vApp/vapp-#{vAppId}/networkConfigSection"
|
202
|
+
}
|
203
|
+
|
204
|
+
response, headers = send_request(params)
|
205
|
+
|
206
|
+
# FIXME: this will return nil if the vApp uses multiple vApp Networks
|
207
|
+
# with Edge devices in natRouted/portForwarding mode.
|
208
|
+
config = response.css('NetworkConfigSection/NetworkConfig/Configuration')
|
209
|
+
|
210
|
+
fenceMode = config.css('/FenceMode').text
|
211
|
+
natType = config.css('/Features/NatService/NatType').text
|
212
|
+
|
213
|
+
raise InvalidStateError, "Invalid request because FenceMode must be set to natRouted." unless fenceMode == "natRouted"
|
214
|
+
raise InvalidStateError, "Invalid request because NatType must be set to portForwarding." unless natType == "portForwarding"
|
215
|
+
|
216
|
+
# Check the routerInfo configuration where the global external IP is defined
|
217
|
+
edgeIp = config.css('/RouterInfo/ExternalIp')
|
218
|
+
edgeIp = edgeIp.text unless edgeIp.nil?
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
##
|
223
|
+
# Merge the Configuration section of a new network and add specific configuration
|
224
|
+
def merge_network_config(vapp_networks, new_network, config)
|
225
|
+
net_configuration = new_network.css('Configuration').first
|
226
|
+
|
227
|
+
fence_mode = new_network.css('FenceMode').first
|
228
|
+
fence_mode.content = config[:fence_mode] || 'isolated'
|
229
|
+
|
230
|
+
network_features = Nokogiri::XML::Node.new "Features", net_configuration
|
231
|
+
firewall_service = Nokogiri::XML::Node.new "FirewallService", network_features
|
232
|
+
firewall_enabled = Nokogiri::XML::Node.new "IsEnabled", firewall_service
|
233
|
+
firewall_enabled.content = config[:firewall_enabled] || "false"
|
234
|
+
|
235
|
+
firewall_service.add_child(firewall_enabled)
|
236
|
+
network_features.add_child(firewall_service)
|
237
|
+
net_configuration.add_child(network_features)
|
238
|
+
|
239
|
+
if config[:parent_network]
|
240
|
+
# At this stage, set itself as parent network
|
241
|
+
parent_network = Nokogiri::XML::Node.new "ParentNetwork", net_configuration
|
242
|
+
parent_network["href"] = "#{@api_url}/network/#{config[:parent_network][:id]}"
|
243
|
+
parent_network["name"] = config[:parent_network][:name]
|
244
|
+
parent_network["type"] = "application/vnd.vmware.vcloud.network+xml"
|
245
|
+
new_network.css('IpScopes').first.add_next_sibling(parent_network)
|
246
|
+
end
|
247
|
+
|
248
|
+
vapp_networks.to_xml.gsub("<PLACEHOLDER/>", new_network.css('Configuration').to_xml)
|
249
|
+
end
|
250
|
+
|
251
|
+
##
|
252
|
+
# Add a new network to a vApp
|
253
|
+
def add_network_to_vapp(vAppId, network_section)
|
254
|
+
params = {
|
255
|
+
'method' => :put,
|
256
|
+
'command' => "/vApp/vapp-#{vAppId}/networkConfigSection"
|
257
|
+
}
|
258
|
+
|
259
|
+
response, headers = send_request(params, network_section, "application/vnd.vmware.vcloud.networkConfigSection+xml")
|
260
|
+
|
261
|
+
task_id = headers[:location].gsub(/.*\/task\//, "")
|
262
|
+
task_id
|
263
|
+
end
|
264
|
+
|
265
|
+
##
|
266
|
+
# Create a fake NetworkConfig node whose content will be replaced later
|
267
|
+
#
|
268
|
+
# Note: this is a hack to avoid wrong merges through Nokogiri
|
269
|
+
# that would add a default: namespace
|
270
|
+
def create_fake_network_node(vapp_networks, network_name)
|
271
|
+
parent_section = vapp_networks.css('NetworkConfigSection').first
|
272
|
+
new_network = Nokogiri::XML::Node.new "NetworkConfig", parent_section
|
273
|
+
new_network['networkName'] = network_name
|
274
|
+
placeholder = Nokogiri::XML::Node.new "PLACEHOLDER", new_network
|
275
|
+
new_network.add_child placeholder
|
276
|
+
parent_section.add_child(new_network)
|
277
|
+
vapp_networks
|
278
|
+
end
|
279
|
+
|
280
|
+
##
|
281
|
+
# Create a fake Configuration node for internal networking
|
282
|
+
def create_internal_network_node(network_config)
|
283
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
284
|
+
xml.Configuration {
|
285
|
+
xml.IpScopes {
|
286
|
+
xml.IpScope {
|
287
|
+
xml.IsInherited(network_config[:is_inherited] || "false")
|
288
|
+
xml.Gateway network_config[:gateway]
|
289
|
+
xml.Netmask network_config[:netmask]
|
290
|
+
xml.Dns1 network_config[:dns1] if network_config[:dns1]
|
291
|
+
xml.Dns2 network_config[:dns2] if network_config[:dns2]
|
292
|
+
xml.DnsSuffix network_config[:dns_suffix] if network_config[:dns_suffix]
|
293
|
+
xml.IsEnabled(network_config[:is_enabled] || true)
|
294
|
+
xml.IpRanges {
|
295
|
+
xml.IpRange {
|
296
|
+
xml.StartAddress network_config[:start_address]
|
297
|
+
xml.EndAddress network_config[:end_address]
|
298
|
+
}
|
299
|
+
}
|
300
|
+
}
|
301
|
+
}
|
302
|
+
xml.FenceMode 'isolated'
|
303
|
+
xml.RetainNetInfoAcrossDeployments(network_config[:retain_info] || false)
|
304
|
+
}
|
305
|
+
end
|
306
|
+
builder.doc
|
307
|
+
end
|
308
|
+
|
309
|
+
##
|
310
|
+
# Create a NetworkConfigSection for a new internal or external network
|
311
|
+
def generate_network_section(vAppId, network, config, type)
|
312
|
+
params = {
|
313
|
+
'method' => :get,
|
314
|
+
'command' => "/vApp/vapp-#{vAppId}/networkConfigSection"
|
315
|
+
}
|
316
|
+
|
317
|
+
vapp_networks, headers = send_request(params)
|
318
|
+
create_fake_network_node(vapp_networks, network[:name])
|
319
|
+
|
320
|
+
if type.to_sym == :internal
|
321
|
+
# Create a network configuration based on the config
|
322
|
+
new_network = create_internal_network_node(config)
|
323
|
+
else
|
324
|
+
# Retrieve the requested network and prepare it for customization
|
325
|
+
new_network = get_base_network(network[:id])
|
326
|
+
end
|
327
|
+
|
328
|
+
merge_network_config(vapp_networks, new_network, config)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module VCloudClient
|
2
|
+
class Connection
|
3
|
+
##
|
4
|
+
# Fetch details about a given vdc:
|
5
|
+
# - description
|
6
|
+
# - vapps
|
7
|
+
# - networks
|
8
|
+
def get_vdc(vdcId)
|
9
|
+
params = {
|
10
|
+
'method' => :get,
|
11
|
+
'command' => "/vdc/#{vdcId}"
|
12
|
+
}
|
13
|
+
|
14
|
+
response, headers = send_request(params)
|
15
|
+
|
16
|
+
name = response.css("Vdc").attribute("name")
|
17
|
+
name = name.text unless name.nil?
|
18
|
+
|
19
|
+
description = response.css("Description").first
|
20
|
+
description = description.text unless description.nil?
|
21
|
+
|
22
|
+
vapps = {}
|
23
|
+
response.css("ResourceEntity[type='application/vnd.vmware.vcloud.vApp+xml']").each do |item|
|
24
|
+
vapps[item['name']] = item['href'].gsub(/.*\/vApp\/vapp\-/, "")
|
25
|
+
end
|
26
|
+
|
27
|
+
networks = {}
|
28
|
+
response.css("Network[type='application/vnd.vmware.vcloud.network+xml']").each do |item|
|
29
|
+
networks[item['name']] = item['href'].gsub(/.*\/network\//, "")
|
30
|
+
end
|
31
|
+
{ :id => vdcId, :name => name, :description => description,
|
32
|
+
:vapps => vapps, :networks => networks }
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Friendly helper method to fetch a Organization VDC Id by name
|
37
|
+
# - Organization object
|
38
|
+
# - Organization VDC Name
|
39
|
+
def get_vdc_id_by_name(organization, vdcName)
|
40
|
+
result = nil
|
41
|
+
|
42
|
+
organization[:vdcs].each do |vdc|
|
43
|
+
if vdc[0].downcase == vdcName.downcase
|
44
|
+
result = vdc[1]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
result
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Friendly helper method to fetch a Organization VDC by name
|
53
|
+
# - Organization object
|
54
|
+
# - Organization VDC Name
|
55
|
+
def get_vdc_by_name(organization, vdcName)
|
56
|
+
result = nil
|
57
|
+
|
58
|
+
organization[:vdcs].each do |vdc|
|
59
|
+
if vdc[0].downcase == vdcName.downcase
|
60
|
+
result = get_vdc(vdc[1])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
result
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|