vcloud-rest 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,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