fog-vcloud-director 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -1 +1,2 @@
1
1
  require 'fog/vcloud_director/compute'
2
+ require 'securerandom'
@@ -10,10 +10,19 @@ module Fog
10
10
 
11
11
  private
12
12
 
13
+ def href(path)
14
+ @endpoint ||= @configuration[:endpoint].to_s.sub(/\/$/, '') # ensure not ending with '/'
15
+ "#{@endpoint}#{path}"
16
+ end
17
+
13
18
  def vapp_attrs
14
19
  attrs = {
15
- :xmlns => 'http://www.vmware.com/vcloud/v1.5',
16
- 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1'
20
+ :xmlns => 'http://www.vmware.com/vcloud/v1.5',
21
+ 'xmlns:vcloud' => 'http://www.vmware.com/vcloud/v1.5',
22
+ 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1',
23
+ 'xmlns:vssd' => 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData',
24
+ 'xmlns:rasd' => 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData',
25
+ 'xmlns:vmw' => 'http://www.vmware.com/schema/ovf'
17
26
  }
18
27
 
19
28
  [:deploy, :powerOn, :name].each do |a|
@@ -30,12 +39,19 @@ module Fog
30
39
 
31
40
  def build_vapp_instantiation_params(xml)
32
41
  xml.Description @configuration[:Description] if @configuration[:Description]
33
-
34
- vapp = @configuration[:InstantiationParams]
35
- if vapp
36
- xml.InstantiationParams {
42
+ return unless @configuration[:vapp_networks] || @configuration[:InstantiationParams]
43
+
44
+ xml.InstantiationParams do
45
+ if (vapp_networks = @configuration[:vapp_networks])
46
+ xml.NetworkConfigSection do
47
+ xml['ovf'].Info
48
+ array_wrap(vapp_networks).each { |vapp_net| vapp_network_section(xml, **vapp_net) }
49
+ end
50
+ # Backwards compatibility
51
+ # TODO: disable inputing InstantiationParams directly as below because it offers bad UX
52
+ elsif (vapp = @configuration[:InstantiationParams])
37
53
  xml.DefaultStorageProfileSection {
38
- xml.StorageProfile vapp[:DefaultStorageProfile]
54
+ xml.StorageProfile vapp[:DefaultStorageProfile]
39
55
  } if (vapp.key? :DefaultStorageProfile)
40
56
  xml.NetworkConfigSection {
41
57
  xml['ovf'].Info
@@ -48,7 +64,7 @@ module Fog
48
64
  }
49
65
  end if vapp[:NetworkConfig]
50
66
  }
51
- }
67
+ end
52
68
  end
53
69
  end
54
70
 
@@ -102,6 +118,9 @@ module Fog
102
118
  xml.ComputerName customization[:ComputerName]
103
119
  }
104
120
  end
121
+ if (hardware = vm[:hardware])
122
+ build_virtual_hardware_section(xml, hardware)
123
+ end
105
124
  }
106
125
  xml.StorageProfile(:href => vm[:StorageProfileHref]) if (vm.key? :StorageProfileHref)
107
126
  }
@@ -115,6 +134,123 @@ module Fog
115
134
  xml.AllEULAsAccepted (@configuration[:AllEULAsAccepted] || true)
116
135
  end
117
136
 
137
+ def build_virtual_hardware_section(xml, hardware)
138
+ xml[:ovf].VirtualHardwareSection do
139
+ xml[:ovf].Info('Virtual hardware requirements')
140
+ virtual_hardware_section_item_mem(xml, **hardware[:memory]) if hardware[:memory]
141
+ virtual_hardware_section_item_cpu(xml, **hardware[:cpu]) if hardware[:cpu]
142
+ array_wrap(hardware[:disk]).each { |disk| virtual_hardware_section_item_hdd(xml, **disk) }
143
+ end
144
+ end
145
+
146
+ def virtual_hardware_section_item_base(xml, type:, name: nil, id: nil)
147
+ id ||= SecureRandom.uuid
148
+ name ||= id
149
+ xml[:ovf].Item do
150
+ xml[:rasd].ResourceType(type)
151
+ xml[:rasd].ElementName(name)
152
+ xml[:rasd].InstanceID(id)
153
+
154
+ yield
155
+
156
+ # RASD elements must be alphabetically sorted
157
+ sort_nodes_by_name(xml)
158
+ end
159
+ end
160
+
161
+ def virtual_hardware_section_item_mem(xml, quantity_mb:, reservation: nil, limit: nil, weight: nil)
162
+ virtual_hardware_section_item_base(xml, :type => 4) do
163
+ xml[:rasd].AllocationUnits('byte * 2^20')
164
+ xml[:rasd].VirtualQuantity(quantity_mb)
165
+ xml[:rasd].Reservation(reservation) if reservation
166
+ xml[:rasd].Limit(limit) if limit
167
+ xml[:rasd].Weight(weight) if weight
168
+ end
169
+ end
170
+
171
+ def virtual_hardware_section_item_cpu(xml, num_cores:, cores_per_socket: nil, reservation: nil, limit: nil, weight: nil)
172
+ virtual_hardware_section_item_base(xml, :type => 3) do
173
+ xml[:rasd].AllocationUnits('hertz * 10^6')
174
+ xml[:rasd].VirtualQuantity(num_cores)
175
+ xml[:rasd].Reservation(reservation) if reservation
176
+ xml[:rasd].Limit(limit) if limit
177
+ xml[:rasd].Weight(weight) if weight
178
+ xml[:vmw].CoresPerSocket(cores_per_socket) if cores_per_socket
179
+ end
180
+ end
181
+
182
+ def virtual_hardware_section_item_hdd(xml, capacity_mb:, id:, address: nil, type: nil, subtype: nil)
183
+ virtual_hardware_section_item_base(xml, :type => 17, :id => id) do
184
+ xml[:rasd].AddressOnParent(address) if address
185
+ attrs = {}
186
+ attrs['vcloud:capacity'] = capacity_mb if capacity_mb
187
+ attrs['vcloud:busType'] = type if type
188
+ attrs['vcloud:busSubType'] = subtype if subtype
189
+ xml[:rasd].HostResource(attrs)
190
+ end
191
+ end
192
+
193
+ def sort_nodes_by_name(xml)
194
+ nodes = xml.parent.children.remove
195
+ nodes.sort_by { |n| "#{n.namespace.prefix}:#{n.name}" }.each { |n| xml.parent.add_child(n) }
196
+ end
197
+
198
+ def array_wrap(val)
199
+ return val if val.kind_of?(Array)
200
+ [val].compact
201
+ end
202
+
203
+ def vapp_network_section(xml, name:, subnet:, description: nil, deployed: nil, parent: nil, parent_name: nil,
204
+ fence_mode: nil, retain: false, external_ip: nil)
205
+ description ||= name
206
+ parent = href("/network/#{parent}") if parent
207
+ fence_mode = calculate_fence_mode(fence_mode, parent, parent_name)
208
+ xml.NetworkConfig(:networkName => name) do
209
+ xml.Description(description)
210
+ xml.IsDeployed(deployed) unless deployed.nil?
211
+ xml.Configuration do
212
+ xml.IpScopes do
213
+ array_wrap(subnet).each { |s| ip_scope_section(xml, **s) }
214
+ end
215
+ attr = {
216
+ :href => parent.to_s
217
+ }
218
+ attr[:name] = parent_name if parent_name
219
+ xml.ParentNetwork(attr) if parent || parent_name
220
+ xml.FenceMode(fence_mode)
221
+ xml.RetainNetInfoAcrossDeployments(retain)
222
+ xml.RouterInfo do
223
+ xml.ExternalIp(external_ip)
224
+ end if external_ip
225
+ end
226
+ end
227
+ end
228
+
229
+ def ip_scope_section(xml, gateway:, netmask:, enabled: true, inherited: false, dns1: nil, dns2: nil, dns_suffix: nil, ip_range: nil)
230
+ xml.IpScope do
231
+ xml.IsInherited(inherited)
232
+ xml.Gateway(gateway)
233
+ xml.Netmask(netmask)
234
+ xml.Dns1(dns1) if dns1
235
+ xml.Dns2(dns2) if dns2
236
+ xml.DnsSuffix(dns_suffix) if dns_suffix
237
+ xml.IsEnabled(enabled)
238
+ xml.IpRanges do
239
+ array_wrap(ip_range).each do |range|
240
+ xml.IpRange do
241
+ xml.StartAddress(range[:start])
242
+ xml.EndAddress(range[:end])
243
+ end
244
+ end
245
+ end if ip_range
246
+ end
247
+ end
248
+
249
+ def calculate_fence_mode(mode, parent, parent_name)
250
+ return 'isolated' unless parent || parent_name
251
+ return 'bridged' unless mode && mode != 'isolated'
252
+ mode
253
+ end
118
254
  end
119
255
  end
120
256
  end
@@ -41,20 +41,25 @@ module Fog
41
41
  options[:vdc_uri] = vdc_end_point(options[:vdc_id])
42
42
  options[:network_uri] = network_end_point(options[:network_id]) if options[:network_id]
43
43
  options[:template_uri] = vapp_template_end_point(options[:template_id]) || raise("template_id option is required")
44
+ options[:endpoint] = end_point
44
45
  options
45
46
  end
46
47
 
47
48
  def generate_instantiate_vapp_template_request(options ={})
48
- #overriding some params so they work with new standardised generator
49
- options[:InstantiationParams] =
50
- {
51
- :NetworkConfig =>
52
- [{
53
- :networkName => options[:network_name],
54
- :networkHref => options[:network_uri],
55
- # :fenceMode => "bridged"
56
- }]
57
- } unless options[:InstantiationParams]
49
+ # TODO: this is old format for customizing vapp networks, please use :vapp_networks instead to let
50
+ # us do the XML building magic for you
51
+ if options.key?(:network_name) || options.key?(:network_uri)
52
+ options[:InstantiationParams] = {
53
+ :NetworkConfig =>
54
+ [{
55
+ :networkName => options[:network_name],
56
+ :networkHref => options[:network_uri],
57
+ # :fenceMode => "bridged"
58
+ }]
59
+ } unless options[:InstantiationParams]
60
+ network_config = options[:InstantiationParams][:NetworkConfig]
61
+ network_config.each_with_index { |_, i| network_config[i][:networkHref] = network_end_point(network_config[i].delete(:networkId)) if network_config[i].key?(:networkId) }
62
+ end
58
63
  options[:name] = options.delete(:vapp_name) if options[:vapp_name]
59
64
  options[:Description] = options.delete(:description) unless options[:Description]
60
65
  if options[:vms_config] then
@@ -64,9 +69,6 @@ module Fog
64
69
  options[:Source] = options.delete(:template_uri) if options[:template_uri]
65
70
  options[:source_vms].each_with_index { |_, i| options[:source_vms][i][:href] = vapp_template_vm_end_point(options[:source_vms][i].delete(:vm_id)) if options[:source_vms][i].has_key?(:vm_id) }
66
71
 
67
- network_config = options[:InstantiationParams][:NetworkConfig]
68
- network_config.each_with_index { |_, i| network_config[i][:networkHref] = network_end_point(network_config[i].delete(:networkId)) if network_config[i].key?(:networkId) }
69
-
70
72
  Fog::Generators::Compute::VcloudDirector::InstantiateVappTemplateParams.new(options).generate_xml
71
73
  end
72
74
 
@@ -1,5 +1,5 @@
1
1
  module Fog
2
2
  module VcloudDirector
3
- VERSION = "0.1.8".freeze
3
+ VERSION = "0.1.9".freeze
4
4
  end
5
5
  end
@@ -0,0 +1,72 @@
1
+ require './spec/vcloud_director/spec_helper.rb'
2
+ require 'minitest/autorun'
3
+ require 'nokogiri'
4
+ require './lib/fog/vcloud_director/generators/compute/compose_common.rb'
5
+
6
+ include Fog::Generators::Compute::VcloudDirector::ComposeCommon
7
+
8
+ describe Fog::Generators::Compute::VcloudDirector::ComposeCommon do
9
+ describe '.calculate_fence_mode' do
10
+ [
11
+ {
12
+ :case => 'default',
13
+ :mode => nil,
14
+ :parent => nil,
15
+ :parent_name => nil,
16
+ :expected => 'isolated'
17
+ },
18
+ {
19
+ :case => 'prevent isolated when parent',
20
+ :mode => 'isolated',
21
+ :parent => 'parent-id',
22
+ :parent_name => nil,
23
+ :expected => 'bridged'
24
+ },
25
+ {
26
+ :case => 'keep natRouted when parent',
27
+ :mode => 'natRouted',
28
+ :parent => 'parent-id',
29
+ :parent_name => nil,
30
+ :expected => 'natRouted'
31
+ },
32
+ {
33
+ :case => 'keep bridged when parent',
34
+ :mode => 'bridged',
35
+ :parent => 'parent-id',
36
+ :parent_name => nil,
37
+ :expected => 'bridged'
38
+ },
39
+ {
40
+ :case => 'prevent bridged when no parent',
41
+ :mode => 'bridged',
42
+ :parent => nil,
43
+ :parent_name => nil,
44
+ :expected => 'isolated'
45
+ },
46
+ {
47
+ :case => 'prevent natRouted when no parent',
48
+ :mode => 'natRouted',
49
+ :parent => nil,
50
+ :parent_name => nil,
51
+ :expected => 'isolated'
52
+ },
53
+ {
54
+ :case => 'prevent isolated when parent_name',
55
+ :mode => 'isolated',
56
+ :parent => nil,
57
+ :parent_name => 'parent-name',
58
+ :expected => 'bridged'
59
+ },
60
+ ].each do |args|
61
+ it args[:case].to_s do
62
+ mode = Fog::Generators::Compute::VcloudDirector::ComposeCommon.send(
63
+ :calculate_fence_mode,
64
+ args[:mode],
65
+ args[:parent],
66
+ args[:parent_name]
67
+ )
68
+ mode.must_equal(args[:expected])
69
+ end
70
+ end
71
+ end
72
+ end
@@ -4,65 +4,543 @@ require 'nokogiri'
4
4
  require './lib/fog/vcloud_director/generators/compute/instantiate_vapp_template_params.rb'
5
5
 
6
6
  describe Fog::Generators::Compute::VcloudDirector::InstantiateVappTemplateParams do
7
-
8
- let(:xml) do
9
- params = {
10
- :name => 'VAPP_NAME',
11
- :Description => 'MY VAPP',
12
- :InstantiationParams => {
13
- :NetworkConfig => [
7
+ describe 'Complete xml' do
8
+ let(:xml) do
9
+ params = {
10
+ :name => 'VAPP_NAME',
11
+ :Description => 'MY VAPP',
12
+ :InstantiationParams => {
13
+ :NetworkConfig => [
14
+ {
15
+ :networkName => 'NETWORK',
16
+ :networkHref => 'http://vcloud/api/network/123456789',
17
+ :fenceMode => 'bridged'
18
+ }
19
+ ]
20
+ },
21
+ :Source => 'http://vcloud/vapp_template/1234',
22
+ :source_vms => [
23
+ {
24
+ :name => 'VM1',
25
+ :href => 'http://vcloud/api/vm/12345',
26
+ :StorageProfileHref => 'http://vcloud/storage/123456789',
27
+ :hardware => {
28
+ :memory => { :quantity_mb => 1024, :reservation => 0, :limit => 1, :weight => 2 },
29
+ :cpu => { :num_cores => 4, :cores_per_socket => 2, :reservation => 0, :limit => 1, :weight => 2 },
30
+ :disks => [
31
+ { :id => 'ID1', :size => 1000 },
32
+ { :id => 'ID2', :size => 2000 }
33
+ ]
34
+ }
35
+ },
14
36
  {
15
- :networkName => 'NETWORK',
16
- :networkHref => 'http://vcloud/api/network/123456789',
17
- :fenceMode => 'bridged'
37
+ :name => 'VM2',
38
+ :href => 'http://vcloud/api/vm/56789',
39
+ :StorageProfileHref => 'http://vcloud/storage/123456789'
18
40
  }
19
41
  ]
42
+
43
+ }
44
+
45
+ output = Fog::Generators::Compute::VcloudDirector::InstantiateVappTemplateParams.new(params).generate_xml
46
+ Nokogiri::XML(output)
47
+ end
48
+
49
+ it 'Generates InstantiateVAppTemplateParams' do
50
+ xml.xpath('//InstantiateVAppTemplateParams').must_be_instance_of Nokogiri::XML::NodeSet
51
+ end
52
+
53
+ it 'Has a valid Network' do
54
+ xml.xpath('//xmlns:NetworkConfig')[0].attr('networkName').must_equal 'NETWORK'
55
+ xml.xpath('//xmlns:ParentNetwork')[0].attr('href').must_equal 'http://vcloud/api/network/123456789'
56
+ end
57
+
58
+ it 'Has valid source VAPP info' do
59
+ node = xml.xpath('//xmlns:Source[@href="http://vcloud/vapp_template/1234"]')
60
+ node.length.must_equal 1
61
+ end
62
+
63
+ it 'Has valid source VM info' do
64
+ xml.xpath('//xmlns:StorageProfile[@href="http://vcloud/storage/123456789"]').length.must_equal 2
65
+ end
66
+
67
+ it 'Allows New VM Parameters' do
68
+ nodes = xml.xpath('//xmlns:VmGeneralParams')
69
+ nodes.length.must_equal 2
70
+ end
71
+ end
72
+
73
+ describe 'Hardware customization' do
74
+ [
75
+ {
76
+ case: 'nil hardware section',
77
+ hardware: nil,
78
+ :expect => ->(out) {}
20
79
  },
21
- :Source => 'http://vcloud/vapp_template/1234',
22
- :source_vms => [
23
- {
24
- :name => 'VM1',
25
- :href => 'http://vcloud/api/vm/12345',
26
- :StorageProfileHref => 'http://vcloud/storage/123456789'
80
+ {
81
+ case: 'nil memory section',
82
+ hardware: {
83
+ :memory => nil
27
84
  },
28
- {
29
- :name => 'VM2',
30
- :href => 'http://vcloud/api/vm/12345',
31
- :StorageProfileHref => 'http://vcloud/storage/123456789'
85
+ :expect => ->(out) {}
86
+ },
87
+ {
88
+ case: 'minimal memory section',
89
+ hardware: {
90
+ :memory => { :quantity_mb => 1024 }
91
+ },
92
+ :expect => lambda do |out|
93
+ mems = hw_items_by_type(out, 4)
94
+ mems.count.must_equal 1
95
+ mem = mems.first
96
+ common_mem_assertions(mem)
97
+ mem.xpath('./rasd:VirtualQuantity').text.must_equal('1024')
98
+ mem.xpath('./rasd:Reservation').must_be_empty
99
+ mem.xpath('./rasd:Limit').must_be_empty
100
+ mem.xpath('./rasd:Weight').must_be_empty
101
+ end
102
+ },
103
+ {
104
+ case: 'full memory section',
105
+ hardware: {
106
+ :memory => { :quantity_mb => 1024, :reservation => 0, :limit => 1, :weight => 2 }
107
+ },
108
+ :expect => lambda do |out|
109
+ mems = hw_items_by_type(out, 4)
110
+ mems.count.must_equal 1
111
+ mem = mems.first
112
+ common_mem_assertions(mem)
113
+ mem.xpath('./rasd:VirtualQuantity').text.must_equal('1024')
114
+ mem.xpath('./rasd:Reservation').text.must_equal('0')
115
+ mem.xpath('./rasd:Limit').text.must_equal('1')
116
+ mem.xpath('./rasd:Weight').text.must_equal('2')
117
+ end
118
+ },
119
+ {
120
+ case: 'nil cpu section',
121
+ hardware: {
122
+ :cpu => nil
123
+ },
124
+ :expect => ->(out) {}
125
+ },
126
+ {
127
+ case: 'minimal cpu section',
128
+ hardware: {
129
+ :cpu => { :num_cores => 4 }
130
+ },
131
+ :expect => lambda do |out|
132
+ cpus = hw_items_by_type(out, 3)
133
+ cpus.count.must_equal 1
134
+ cpu = cpus.first
135
+ common_cpu_assertions(cpu)
136
+ cpu.xpath('./rasd:VirtualQuantity').text.must_equal('4')
137
+ cpu.xpath('./rasd:Reservation').must_be_empty
138
+ cpu.xpath('./rasd:Limit').must_be_empty
139
+ cpu.xpath('./rasd:Weight').must_be_empty
140
+ cpu.xpath('./vmw:CoresPerSocket').must_be_empty
141
+ end
142
+ },
143
+ {
144
+ case: 'full cpu section',
145
+ hardware: {
146
+ :cpu => { :num_cores => 4, :cores_per_socket => 2, :reservation => 0, :limit => 1, :weight => 2 }
147
+ },
148
+ :expect => lambda do |out|
149
+ cpus = hw_items_by_type(out, 3)
150
+ cpus.count.must_equal 1
151
+ cpu = cpus.first
152
+ common_cpu_assertions(cpu)
153
+ cpu.xpath('./rasd:VirtualQuantity').text.must_equal('4')
154
+ cpu.xpath('./rasd:Reservation').text.must_equal('0')
155
+ cpu.xpath('./rasd:Limit').text.must_equal('1')
156
+ cpu.xpath('./rasd:Weight').text.must_equal('2')
157
+ cpu.xpath('./vmw:CoresPerSocket').text.must_equal('2')
158
+ end
159
+ },
160
+ {
161
+ case: 'nil disk section',
162
+ hardware: {
163
+ :disk => nil
164
+ },
165
+ :expect => ->(out) {}
166
+ },
167
+ {
168
+ case: 'minimal disk section',
169
+ hardware: {
170
+ :disk => { :id => 2000, :capacity_mb => 1024 }
171
+ },
172
+ :expect => lambda do |out|
173
+ hdds = hw_items_by_type(out, 17)
174
+ hdds.count.must_equal 1
175
+ hdd = hdds.first
176
+ common_hw_assertions(hdd)
177
+ hdd.xpath('./rasd:InstanceID').text.must_equal('2000')
178
+ hdd.xpath('./rasd:HostResource').text.must_be_empty
179
+ hdd.xpath('./rasd:HostResource/@vcloud:capacity').text.must_equal('1024')
180
+ hdd.xpath('./rasd:HostResource/@vcloud:busType').must_be_empty
181
+ hdd.xpath('./rasd:HostResource/@vcloud:busSubType').must_be_empty
182
+ hdd.xpath('./rasd:AddressOnParent').must_be_empty
183
+ end
184
+ },
185
+ {
186
+ case: 'full disk section',
187
+ hardware: {
188
+ :disk => { :id => 2000, :capacity_mb => 1024, :address => 0, :type => 6, :subtype => 'VirtualSCSI' }
189
+ },
190
+ :expect => lambda do |out|
191
+ hdds = hw_items_by_type(out, 17)
192
+ hdds.count.must_equal 1
193
+ hdd = hdds.first
194
+ common_hw_assertions(hdd)
195
+ hdd.xpath('./rasd:InstanceID').text.must_equal('2000')
196
+ hdd.xpath('./rasd:HostResource').text.must_be_empty
197
+ hdd.xpath('./rasd:HostResource/@vcloud:capacity').text.must_equal('1024')
198
+ hdd.xpath('./rasd:HostResource/@vcloud:busType').text.must_equal('6')
199
+ hdd.xpath('./rasd:HostResource/@vcloud:busSubType').text.must_equal('VirtualSCSI')
200
+ hdd.xpath('./rasd:AddressOnParent').text.must_equal('0')
201
+ end
202
+ },
203
+ {
204
+ case: 'two disks',
205
+ hardware: {
206
+ :disk => [
207
+ { :id => 2000, :capacity_mb => 1024 },
208
+ { :id => 3000, :capacity_mb => 2048 }
209
+ ]
210
+ },
211
+ :expect => lambda do |out|
212
+ hdds = hw_items_by_type(out, 17)
213
+ hdds.count.must_equal 2
214
+ hdds.each { |hdd| common_hw_assertions(hdd) }
215
+ end
216
+ }
217
+ ].each do |args|
218
+ it args[:case].to_s do
219
+ input = {
220
+ :name => 'VAPP_NAME',
221
+ :source_vms => [
222
+ {
223
+ :name => 'VM1',
224
+ :hardware => args[:hardware]
225
+ }
226
+ ]
32
227
  }
33
- ]
34
-
35
- }
228
+ output = Fog::Generators::Compute::VcloudDirector::InstantiateVappTemplateParams.new(input).generate_xml
229
+ args[:expect].call(Nokogiri::XML(output))
230
+ end
231
+ end
36
232
 
37
- output = Fog::Generators::Compute::VcloudDirector::InstantiateVappTemplateParams.new(params).generate_xml
38
- Nokogiri::XML(output)
39
- end
40
-
41
- it "Generates InstantiateVAppTemplateParams" do
42
- xml.xpath('//InstantiateVAppTemplateParams').must_be_instance_of Nokogiri::XML::NodeSet
43
- end
44
-
45
- it "Has a valid Network" do
46
- node = xml.xpath('//xmlns:NetworkConfigSection')
47
-
48
- xml.xpath("//xmlns:NetworkConfig")[0].attr('networkName').must_equal "NETWORK"
49
- xml.xpath('//xmlns:ParentNetwork')[0].attr('href').must_equal 'http://vcloud/api/network/123456789'
50
-
233
+ def self.hw_items_by_type(xml, type)
234
+ xml.xpath("//xmlns:SourcedItem/xmlns:InstantiationParams/ovf:VirtualHardwareSection/ovf:Item[./rasd:ResourceType = '#{type}']")
235
+ end
236
+
237
+ def self.common_hw_assertions(xml)
238
+ # RASD elements must be alphabetically sorted
239
+ children = xml.children.select { |n| n.type == 1 }.map { |n| "#{n.namespace.prefix}:#{n.name}" }
240
+ sorted = children.sort
241
+ children.must_equal(sorted)
242
+
243
+ xml.xpath('./rasd:ElementName').text.length.must_be :>, 0
244
+ xml.xpath('./rasd:InstanceID').text.length.must_be :>, 0
245
+ end
246
+
247
+ def self.common_mem_assertions(xml)
248
+ common_hw_assertions(xml)
249
+ xml.xpath('./rasd:VirtualQuantity').text.length.must_be :>, 0
250
+ xml.xpath('./rasd:AllocationUnits').text.must_equal('byte * 2^20')
251
+ end
252
+
253
+ def self.common_cpu_assertions(xml)
254
+ common_hw_assertions(xml)
255
+ xml.xpath('./rasd:VirtualQuantity').text.length.must_be :>, 0
256
+ xml.xpath('./rasd:AllocationUnits').text.must_equal('hertz * 10^6')
257
+ end
51
258
  end
52
-
53
- it "Has valid source VAPP info" do
54
- node = xml.xpath('//xmlns:Source[@href="http://vcloud/vapp_template/1234"]')
55
- node.length.must_equal 1
259
+
260
+ describe 'vApp Network customization' do
261
+ [
262
+ {
263
+ :case => 'minimal vapp networking',
264
+ :vapp_networks => [
265
+ {
266
+ :name => 'network',
267
+ :subnet => [
268
+ {
269
+ :gateway => '1.2.3.4',
270
+ :netmask => '255.255.255.0'
271
+ }
272
+ ]
273
+ }
274
+ ],
275
+ :expect => lambda do |vapp_net_conf|
276
+ common_vapp_network_config_assertions(vapp_net_conf)
277
+ vapp_net_conf.xpath('./vcloud:NetworkConfig/@networkName').text.must_equal 'network'
278
+ conf = vapp_net_conf.xpath('./vcloud:NetworkConfig/vcloud:Configuration')
279
+ conf.xpath('./vcloud:FenceMode').text.must_equal 'isolated'
280
+ conf.xpath('./vcloud:ParentNetwork').text.must_be_empty
281
+ subnet = conf.xpath('./vcloud:IpScopes/vcloud:IpScope')
282
+ subnet.xpath('./vcloud:Gateway').text.must_equal '1.2.3.4'
283
+ subnet.xpath('./vcloud:Netmask').text.must_equal '255.255.255.0'
284
+ end
285
+ },
286
+ {
287
+ :case => 'full networking',
288
+ :vapp_networks => [
289
+ {
290
+ :name => 'network',
291
+ :description => 'VM Network Description',
292
+ :deployed => true,
293
+ :parent => 'parent-network-id',
294
+ :fence_mode => 'bridged',
295
+ :retain => false,
296
+ :external_ip => '17.17.17.17',
297
+ :subnet => [
298
+ {
299
+ :enabled => true,
300
+ :inherited => false,
301
+ :gateway => '1.2.3.4',
302
+ :netmask => '255.255.255.0',
303
+ :dns1 => '5.6.7.8',
304
+ :dns2 => '9.10.11.12',
305
+ :dns_suffix => 'dns-suffix',
306
+ :ip_range => [{ :start => '192.168.254.100', :end => '192.168.254.199' }]
307
+ }
308
+ ]
309
+ }
310
+ ],
311
+ :expect => lambda do |vapp_net_conf|
312
+ common_vapp_network_config_assertions(vapp_net_conf)
313
+ vapp_net_conf.xpath('./vcloud:NetworkConfig/@networkName').text.must_equal 'network'
314
+ vapp_net_conf.xpath('./vcloud:NetworkConfig/vcloud:Description').text.must_equal 'VM Network Description'
315
+ vapp_net_conf.xpath('./vcloud:NetworkConfig/vcloud:IsDeployed').text.must_equal 'true'
316
+ conf = vapp_net_conf.xpath('./vcloud:NetworkConfig/vcloud:Configuration')
317
+ conf.xpath('./vcloud:FenceMode').text.must_equal 'bridged'
318
+ conf.xpath('./vcloud:ParentNetwork/@href').text.must_equal 'ENDPOINT/network/parent-network-id'
319
+ conf.xpath('./vcloud:RetainNetInfoAcrossDeployments').text.must_equal 'false'
320
+ conf.xpath('./vcloud:RouterInfo/vcloud:ExternalIp').text.must_equal '17.17.17.17'
321
+ subnet = conf.xpath('./vcloud:IpScopes/vcloud:IpScope')
322
+ subnet.xpath('./vcloud:IsEnabled').text.must_equal 'true'
323
+ subnet.xpath('./vcloud:IsInherited').text.must_equal 'false'
324
+ subnet.xpath('./vcloud:Gateway').text.must_equal '1.2.3.4'
325
+ subnet.xpath('./vcloud:Netmask').text.must_equal '255.255.255.0'
326
+ subnet.xpath('./vcloud:Dns1').text.must_equal '5.6.7.8'
327
+ subnet.xpath('./vcloud:Dns2').text.must_equal '9.10.11.12'
328
+ ip_range = subnet.xpath('./vcloud:IpRanges/vcloud:IpRange')
329
+ ip_range.xpath('./vcloud:StartAddress').text.must_equal '192.168.254.100'
330
+ ip_range.xpath('./vcloud:EndAddress').text.must_equal '192.168.254.199'
331
+ end
332
+ },
333
+ {
334
+ :case => 'two vapp networks',
335
+ :vapp_networks => [
336
+ {
337
+ :name => 'network1',
338
+ :subnet => [
339
+ {
340
+ :gateway => '1.1.1.1',
341
+ :netmask => '255.255.255.1'
342
+ }
343
+ ]
344
+ },
345
+ {
346
+ :name => 'network2',
347
+ :subnet => [
348
+ {
349
+ :gateway => '2.2.2.2',
350
+ :netmask => '255.255.255.2'
351
+ }
352
+ ]
353
+ }
354
+ ],
355
+ :expect => lambda do |vapp_net_conf|
356
+ common_vapp_network_config_assertions(vapp_net_conf)
357
+ vapp_net_conf.xpath('./vcloud:NetworkConfig').count.must_equal 2
358
+ vapp_net_conf.xpath('./vcloud:NetworkConfig[1]/@networkName').text.must_equal 'network1'
359
+ vapp_net_conf.xpath('./vcloud:NetworkConfig[2]/@networkName').text.must_equal 'network2'
360
+ conf1 = vapp_net_conf.xpath('./vcloud:NetworkConfig[1]/vcloud:Configuration')
361
+ subnet1 = conf1.xpath('./vcloud:IpScopes/vcloud:IpScope')
362
+ subnet1.xpath('./vcloud:Gateway').text.must_equal '1.1.1.1'
363
+ subnet1.xpath('./vcloud:Netmask').text.must_equal '255.255.255.1'
364
+ conf2 = vapp_net_conf.xpath('./vcloud:NetworkConfig[2]/vcloud:Configuration')
365
+ subnet2 = conf2.xpath('./vcloud:IpScopes/vcloud:IpScope')
366
+ subnet2.xpath('./vcloud:Gateway').text.must_equal '2.2.2.2'
367
+ subnet2.xpath('./vcloud:Netmask').text.must_equal '255.255.255.2'
368
+ end
369
+ }
370
+ ].each do |args|
371
+ it args[:case].to_s do
372
+ input = {
373
+ :name => 'VAPP_NAME',
374
+ :vapp_networks => args[:vapp_networks],
375
+ :endpoint => 'ENDPOINT'
376
+ }
377
+ output = Fog::Generators::Compute::VcloudDirector::InstantiateVappTemplateParams.new(input).generate_xml
378
+ args[:expect].call(vapp_network_config(Nokogiri::XML(output)))
379
+ end
380
+ end
381
+
382
+ def vapp_network_config(xml)
383
+ xml.xpath('//xmlns:NetworkConfigSection')
384
+ end
385
+
386
+ def self.common_vapp_network_config_assertions(vapp_net_conf)
387
+ vapp_net_conf.count.must_equal 1
388
+ end
56
389
  end
57
-
58
- it "Has valid source VM info" do
59
-
60
- xml.xpath('//xmlns:StorageProfile[@href="http://vcloud/storage/123456789"]').length.must_equal 2
390
+
391
+ describe 'Guest customization' do
392
+ [
393
+ {
394
+ :case => 'hostname',
395
+ :guest_customization => {
396
+ :ComputerName => 'hostname'
397
+ },
398
+ :expect => lambda do |guest_customization|
399
+ common_guest_customization_assertions(guest_customization)
400
+ hostnames = guest_customization.xpath('./xmlns:ComputerName')
401
+ hostnames.count.must_equal 1
402
+ hostname = hostnames.first
403
+ hostname.text.must_equal 'hostname'
404
+ end
405
+ }
406
+ ].each do |args|
407
+ it args[:case].to_s do
408
+ input = {
409
+ :name => 'VAPP_NAME',
410
+ :source_vms => [
411
+ {
412
+ :name => 'VM1',
413
+ :guest_customization => args[:guest_customization]
414
+ }
415
+ ]
416
+ }
417
+ output = Fog::Generators::Compute::VcloudDirector::InstantiateVappTemplateParams.new(input).generate_xml
418
+ args[:expect].call(guest_customization(Nokogiri::XML(output)))
419
+ end
420
+ end
421
+
422
+ def guest_customization(xml)
423
+ xml.xpath('//xmlns:SourcedItem/xmlns:InstantiationParams/xmlns:GuestCustomizationSection')
424
+ end
425
+
426
+ def self.common_guest_customization_assertions(guest_customization)
427
+ guest_customization.count.must_equal 1
428
+ end
61
429
  end
62
-
63
- it "Allows New VM Parameters" do
64
- nodes = xml.xpath('//xmlns:VmGeneralParams')
65
- nodes.length.must_equal 2
430
+
431
+ describe 'NIC connection customization' do
432
+ [
433
+ {
434
+ :case => 'connect NIC#0 in DHCP mode',
435
+ :networks => [
436
+ {
437
+ :networkName => 'network',
438
+ :IpAddressAllocationMode => 'DHCP',
439
+ :IsConnected => true
440
+ }
441
+ ],
442
+ :expect => lambda do |networks|
443
+ common_networks_assertions(networks)
444
+ network = networks.first
445
+ network.xpath('./@network').text.must_equal 'network'
446
+ network.xpath('./xmlns:NetworkConnectionIndex').text.must_equal '0'
447
+ network.xpath('./xmlns:IpAddressAllocationMode').text.must_equal 'DHCP'
448
+ network.xpath('./xmlns:IpAddress').text.must_be_empty
449
+ network.xpath('./xmlns:IsConnected').text.must_equal 'true'
450
+ end
451
+ },
452
+ {
453
+ :case => 'connect NIC#0 in MANUAL mode',
454
+ :networks => [
455
+ {
456
+ :networkName => 'network',
457
+ :IpAddressAllocationMode => 'MANUAL',
458
+ :IpAddress => '1.2.3.4',
459
+ :IsConnected => true
460
+ }
461
+ ],
462
+ :expect => lambda do |networks|
463
+ common_networks_assertions(networks)
464
+ network = networks.first
465
+ network.xpath('./@network').text.must_equal 'network'
466
+ network.xpath('./xmlns:NetworkConnectionIndex').text.must_equal '0'
467
+ network.xpath('./xmlns:IpAddressAllocationMode').text.must_equal 'MANUAL'
468
+ network.xpath('./xmlns:IpAddress').text.must_equal '1.2.3.4'
469
+ network.xpath('./xmlns:IsConnected').text.must_equal 'true'
470
+ end
471
+ },
472
+ {
473
+ :case => 'connect NIC#0 in POOL mode',
474
+ :networks => [
475
+ {
476
+ :networkName => 'network',
477
+ :IpAddressAllocationMode => 'POOL',
478
+ :IsConnected => true
479
+ }
480
+ ],
481
+ :expect => lambda do |networks|
482
+ common_networks_assertions(networks)
483
+ network = networks.first
484
+ network.xpath('./@network').text.must_equal 'network'
485
+ network.xpath('./xmlns:NetworkConnectionIndex').text.must_equal '0'
486
+ network.xpath('./xmlns:IpAddressAllocationMode').text.must_equal 'POOL'
487
+ network.xpath('./xmlns:IpAddress').text.must_be_empty
488
+ network.xpath('./xmlns:IsConnected').text.must_equal 'true'
489
+ end
490
+ },
491
+ {
492
+ :case => 'connect NIC#0 and NIC#1',
493
+ :networks => [
494
+ {
495
+ :networkName => 'network0',
496
+ :IpAddressAllocationMode => 'DHCP',
497
+ :IsConnected => true
498
+ },
499
+ {
500
+ :networkName => 'network1',
501
+ :IpAddressAllocationMode => 'MANUAL',
502
+ :IpAddress => '1.2.3.4',
503
+ :IsConnected => true
504
+ }
505
+ ],
506
+ :expect => lambda do |networks|
507
+ networks.count.must_equal 2
508
+ nic0_network = networks[0]
509
+ nic0_network.xpath('./@network').text.must_equal 'network0'
510
+ nic0_network.xpath('./xmlns:NetworkConnectionIndex').text.must_equal '0'
511
+ nic0_network.xpath('./xmlns:IpAddressAllocationMode').text.must_equal 'DHCP'
512
+ nic0_network.xpath('./xmlns:IpAddress').text.must_be_empty
513
+ nic0_network.xpath('./xmlns:IsConnected').text.must_equal 'true'
514
+ nic1_network = networks[1]
515
+ nic1_network.xpath('./@network').text.must_equal 'network1'
516
+ nic1_network.xpath('./xmlns:NetworkConnectionIndex').text.must_equal '1'
517
+ nic1_network.xpath('./xmlns:IpAddressAllocationMode').text.must_equal 'MANUAL'
518
+ nic1_network.xpath('./xmlns:IpAddress').text.must_equal '1.2.3.4'
519
+ nic1_network.xpath('./xmlns:IsConnected').text.must_equal 'true'
520
+ end
521
+ }
522
+ ].each do |args|
523
+ it args[:case].to_s do
524
+ input = {
525
+ :name => 'VAPP_NAME',
526
+ :source_vms => [
527
+ {
528
+ :name => 'VM1',
529
+ :networks => args[:networks]
530
+ }
531
+ ]
532
+ }
533
+ output = Fog::Generators::Compute::VcloudDirector::InstantiateVappTemplateParams.new(input).generate_xml
534
+ args[:expect].call(networks(Nokogiri::XML(output)))
535
+ end
536
+ end
537
+
538
+ def networks(xml)
539
+ xml.xpath('//xmlns:SourcedItem/xmlns:InstantiationParams/xmlns:NetworkConnectionSection/xmlns:NetworkConnection')
540
+ end
541
+
542
+ def self.common_networks_assertions(networks)
543
+ networks.count.must_equal 1
544
+ end
66
545
  end
67
-
68
- end
546
+ end