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.
@@ -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