vcloud-rest 0.3.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|