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.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/docs/examples-vapp-instantiate.md +120 -0
- data/docs/web-server-with-db-vapp.xml +364 -0
- data/lib/fog/vcloud_director.rb +1 -0
- data/lib/fog/vcloud_director/generators/compute/compose_common.rb +144 -8
- data/lib/fog/vcloud_director/requests/compute/instantiate_vapp_template.rb +15 -13
- data/lib/fog/vcloud_director/version.rb +1 -1
- data/spec/vcloud_director/generators/compute/compose_common_spec.rb +72 -0
- data/spec/vcloud_director/generators/compute/instantiate_vapp_template_params_spec.rb +529 -51
- data/spec/vcloud_director/spec_helper.rb +2 -0
- metadata +7 -3
data/lib/fog/vcloud_director.rb
CHANGED
@@ -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
|
16
|
-
'xmlns:
|
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
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
-
#
|
49
|
-
|
50
|
-
|
51
|
-
:
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
|
@@ -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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
:
|
16
|
-
:
|
17
|
-
:
|
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
|
-
|
22
|
-
|
23
|
-
{
|
24
|
-
:
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
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
|