fog-vcloud-director 0.1.8 → 0.1.9

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