bosh_vcloud_cpi 0.4.8
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.
- data/README +26 -0
- data/Rakefile +50 -0
- data/lib/cloud/vcloud/cloud.rb +633 -0
- data/lib/cloud/vcloud/const.rb +27 -0
- data/lib/cloud/vcloud/util.rb +21 -0
- data/lib/cloud/vcloud/version.rb +7 -0
- data/lib/cloud/vcloud.rb +30 -0
- data/spec/assets/test-deployment-manifest.yml +183 -0
- data/spec/assets/test-director-config.yml +73 -0
- data/spec/assets/valid_stemcell.tgz +0 -0
- data/spec/spec_helper.rb +135 -0
- data/spec/unit/cloud_spec.rb +506 -0
- metadata +131 -0
@@ -0,0 +1,633 @@
|
|
1
|
+
$: << File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require "ruby_vcloud_sdk"
|
4
|
+
require "cloud/vcloud/const"
|
5
|
+
require "cloud/vcloud/util"
|
6
|
+
|
7
|
+
require "digest/sha1"
|
8
|
+
require "fileutils"
|
9
|
+
require "logger"
|
10
|
+
require "uuidtools"
|
11
|
+
require "yajl"
|
12
|
+
require "const"
|
13
|
+
require "thread"
|
14
|
+
|
15
|
+
module VCloudCloud
|
16
|
+
|
17
|
+
class Cloud
|
18
|
+
|
19
|
+
def initialize(options)
|
20
|
+
@logger = Bosh::Clouds::Config.logger
|
21
|
+
VCloudSdk::Config.configure({ "logger" => @logger })
|
22
|
+
@logger.debug("Input cloud options: #{options.inspect}")
|
23
|
+
|
24
|
+
@agent_properties = options["agent"]
|
25
|
+
vcds = options["vcds"]
|
26
|
+
raise ArgumentError, "Invalid number of VCDs" unless vcds.size == 1
|
27
|
+
@vcd = vcds[0]
|
28
|
+
|
29
|
+
finalize_options
|
30
|
+
@control = @vcd["control"]
|
31
|
+
@retries = @control["retries"]
|
32
|
+
@logger.info("VCD cloud options: #{options.inspect}")
|
33
|
+
|
34
|
+
@client_lock = Mutex.new
|
35
|
+
|
36
|
+
at_exit { destroy_client }
|
37
|
+
end
|
38
|
+
|
39
|
+
def client()
|
40
|
+
@client_lock.synchronize {
|
41
|
+
if @client.nil?
|
42
|
+
create_client
|
43
|
+
else
|
44
|
+
begin
|
45
|
+
@client.get_ovdc
|
46
|
+
@client
|
47
|
+
rescue VCloudSdk::CloudError => e
|
48
|
+
log_exception("validate, creating new session.", e)
|
49
|
+
create_client
|
50
|
+
end
|
51
|
+
end
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_stemcell(image, _)
|
56
|
+
@client = client
|
57
|
+
|
58
|
+
with_thread_name("create_stemcell(#{image}, _)") do
|
59
|
+
@logger.debug("create_stemcell #{image} #{_}")
|
60
|
+
result = nil
|
61
|
+
Dir.mktmpdir do |temp_dir|
|
62
|
+
@logger.info("Extracting stemcell to: #{temp_dir}")
|
63
|
+
output = `tar -C #{temp_dir} -xzf #{image} 2>&1`
|
64
|
+
raise "Corrupt image, tar exit status: #{$?.exitstatus} output:" +
|
65
|
+
"#{output}" if $?.exitstatus != 0
|
66
|
+
|
67
|
+
ovf_file = Dir.entries(temp_dir).find {
|
68
|
+
|entry| File.extname(entry) == ".ovf" }
|
69
|
+
raise "Missing OVF" unless ovf_file
|
70
|
+
ovf_file = File.join(temp_dir, ovf_file)
|
71
|
+
|
72
|
+
name = "sc-#{generate_unique_name}"
|
73
|
+
@logger.info("Generated name: #{name}")
|
74
|
+
|
75
|
+
@logger.info("Uploading #{ovf_file}")
|
76
|
+
result = @client.upload_vapp_template(name, temp_dir).urn
|
77
|
+
end
|
78
|
+
|
79
|
+
@logger.info("Stemcell created as catalog vApp with ID #{result}.")
|
80
|
+
result
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete_stemcell(catalog_vapp_id)
|
85
|
+
@client = client
|
86
|
+
|
87
|
+
with_thread_name("delete_stemcell(#{catalog_vapp_id})") do
|
88
|
+
@logger.debug("delete_stemcell #{catalog_vapp_id}")
|
89
|
+
|
90
|
+
# shadow VMs (stemcell replicas) get deleted serially,
|
91
|
+
# and upon failure to delete they must be deleted manually
|
92
|
+
# from VCD "stranded items"
|
93
|
+
@client.delete_catalog_vapp(catalog_vapp_id)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def reconfigure_vm(vapp, resource_pool, networks, environment)
|
98
|
+
vm = get_vm(vapp)
|
99
|
+
ram_mb = Integer(resource_pool["ram"])
|
100
|
+
cpu = Integer(resource_pool["cpu"])
|
101
|
+
disk_mb = Integer(resource_pool["disk"])
|
102
|
+
|
103
|
+
disks = vm.hardware_section.hard_disks
|
104
|
+
@logger.debug("disks = #{disks.inspect}")
|
105
|
+
raise IndexError, "Invalid number of VM hard disks" unless disks.size == 1
|
106
|
+
system_disk = disks[0]
|
107
|
+
disks_previous = Array.new(disks)
|
108
|
+
|
109
|
+
add_vapp_networks(vapp, networks)
|
110
|
+
|
111
|
+
@logger.info("Reconfiguring VM hardware: #{ram_mb} MB RAM, #{cpu} CPU, " +
|
112
|
+
"#{disk_mb} MB disk, #{networks}.")
|
113
|
+
@client.reconfigure_vm(vm) do |v|
|
114
|
+
v.name = vapp.name
|
115
|
+
v.description = @vcd["entities"]["description"]
|
116
|
+
v.change_cpu_count(cpu)
|
117
|
+
v.change_memory(ram_mb)
|
118
|
+
v.add_hard_disk(disk_mb)
|
119
|
+
v.delete_nic(*vm.hardware_section.nics)
|
120
|
+
add_vm_nics(v, networks)
|
121
|
+
end
|
122
|
+
|
123
|
+
delete_vapp_networks(vapp, networks)
|
124
|
+
|
125
|
+
vapp, vm = get_vapp_vm_by_vapp_id(vapp.urn)
|
126
|
+
ephemeral_disk = get_newly_added_disk(vm, disks_previous)
|
127
|
+
|
128
|
+
# prepare guest customization settings
|
129
|
+
network_env = generate_network_env(vm.hardware_section.nics, networks)
|
130
|
+
disk_env = generate_disk_env(system_disk, ephemeral_disk)
|
131
|
+
env = generate_agent_env(vapp.name, vm, vapp.name, network_env, disk_env)
|
132
|
+
env["env"] = environment
|
133
|
+
@logger.info("Setting VM env: #{vapp.urn} #{env.inspect}")
|
134
|
+
set_agent_env(vm, env)
|
135
|
+
|
136
|
+
@logger.info("Powering on vApp: #{vapp.urn}")
|
137
|
+
@client.power_on_vapp(vapp)
|
138
|
+
rescue VCloudSdk::CloudError
|
139
|
+
delete_vm(vapp.urn)
|
140
|
+
raise
|
141
|
+
end
|
142
|
+
|
143
|
+
def create_vm(agent_id, catalog_vapp_id, resource_pool, networks,
|
144
|
+
disk_locality = nil, environment = {})
|
145
|
+
@client = client
|
146
|
+
|
147
|
+
with_thread_name("create_vm(#{agent_id}, ...)") do
|
148
|
+
Util.retry_operation("create_vm(#{agent_id}, ...)", @retries['cpi'],
|
149
|
+
@control['backoff']) do
|
150
|
+
@logger.info("Creating VM: #{agent_id}")
|
151
|
+
@logger.debug("networks: #{networks.inspect}")
|
152
|
+
|
153
|
+
locality = independent_disks(disk_locality)
|
154
|
+
|
155
|
+
vapp = @client.instantiate_vapp_template(
|
156
|
+
catalog_vapp_id, agent_id, # vapp name
|
157
|
+
@vcd["entities"]["description"], locality)
|
158
|
+
@logger.debug("Instantiated vApp: id=#{vapp.urn} name=#{vapp.name}")
|
159
|
+
|
160
|
+
reconfigure_vm(vapp, resource_pool, networks, environment)
|
161
|
+
|
162
|
+
@logger.info("Created VM: #{agent_id} as #{vapp.urn}")
|
163
|
+
vapp.urn
|
164
|
+
end
|
165
|
+
end
|
166
|
+
rescue VCloudSdk::CloudError => e
|
167
|
+
log_exception("create vApp", e)
|
168
|
+
raise e
|
169
|
+
end
|
170
|
+
|
171
|
+
def delete_vm(vapp_id)
|
172
|
+
@client = client
|
173
|
+
|
174
|
+
with_thread_name("delete_vm(#{vapp_id}, ...)") do
|
175
|
+
Util.retry_operation("delete_vm(#{vapp_id}, ...)", @retries['cpi'],
|
176
|
+
@control['backoff']) do
|
177
|
+
@logger.info("Deleting vApp: #{vapp_id}")
|
178
|
+
vapp = @client.get_vapp(vapp_id)
|
179
|
+
vm = get_vm(vapp)
|
180
|
+
vm_name = vm.name
|
181
|
+
|
182
|
+
begin
|
183
|
+
@client.power_off_vapp(vapp)
|
184
|
+
rescue VCloudSdk::VappSuspendedError => e
|
185
|
+
@client.discard_suspended_state_vapp(vapp)
|
186
|
+
@client.power_off_vapp(vapp)
|
187
|
+
end
|
188
|
+
del_vapp = @vcd['debug']['delete_vapp']
|
189
|
+
@client.delete_vapp(vapp) if del_vapp
|
190
|
+
@logger.info("Deleting ISO #{vm_name}")
|
191
|
+
@client.delete_catalog_media(vm_name) if del_vapp
|
192
|
+
@logger.info("Deleted vApp: #{vapp_id}")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
rescue VCloudSdk::CloudError => e
|
196
|
+
log_exception("delete vApp #{vapp_id}", e)
|
197
|
+
raise e
|
198
|
+
end
|
199
|
+
|
200
|
+
def reboot_vm(vapp_id)
|
201
|
+
@client = client
|
202
|
+
|
203
|
+
with_thread_name("reboot_vm(#{vapp_id}, ...)") do
|
204
|
+
Util.retry_operation("reboot_vm(#{vapp_id}, ...)", @retries['cpi'],
|
205
|
+
@control['backoff']) do
|
206
|
+
@logger.info("Rebooting vApp: #{vapp_id}")
|
207
|
+
vapp = @client.get_vapp(vapp_id)
|
208
|
+
begin
|
209
|
+
@client.reboot_vapp(vapp)
|
210
|
+
rescue VCloudSdk::VappPoweredOffError => e
|
211
|
+
@client.power_on_vapp(vapp)
|
212
|
+
rescue VCloudSdk::VappSuspendedError => e
|
213
|
+
@client.discard_suspended_state_vapp(vapp)
|
214
|
+
@client.power_on_vapp(vapp)
|
215
|
+
end
|
216
|
+
@logger.info("Rebooted vApp: #{vapp_id}")
|
217
|
+
end
|
218
|
+
end
|
219
|
+
rescue VCloudSdk::CloudError => e
|
220
|
+
log_exception("reboot vApp #{vapp_id}", e)
|
221
|
+
raise e
|
222
|
+
end
|
223
|
+
|
224
|
+
def configure_networks(vapp_id, networks)
|
225
|
+
@client = client
|
226
|
+
|
227
|
+
with_thread_name("configure_networks(#{vapp_id}, ...)") do
|
228
|
+
Util.retry_operation("configure_networks(#{vapp_id}, ...)",
|
229
|
+
@retries['cpi'], @control['backoff']) do
|
230
|
+
@logger.info("Reconfiguring vApp networks: #{vapp_id}")
|
231
|
+
vapp, vm = get_vapp_vm_by_vapp_id(vapp_id)
|
232
|
+
@logger.debug("Powering off #{vapp.name}.")
|
233
|
+
begin
|
234
|
+
@client.power_off_vapp(vapp)
|
235
|
+
rescue VCloudSdk::VappSuspendedError => e
|
236
|
+
@client.discard_suspended_state_vapp(vapp)
|
237
|
+
@client.power_off_vapp(vapp)
|
238
|
+
end
|
239
|
+
|
240
|
+
add_vapp_networks(vapp, networks)
|
241
|
+
@client.reconfigure_vm(vm) do |v|
|
242
|
+
v.delete_nic(*vm.hardware_section.nics)
|
243
|
+
add_vm_nics(v, networks)
|
244
|
+
end
|
245
|
+
delete_vapp_networks(vapp, networks)
|
246
|
+
|
247
|
+
vapp, vm = get_vapp_vm_by_vapp_id(vapp_id)
|
248
|
+
env = get_current_agent_env(vm)
|
249
|
+
env["networks"] = generate_network_env(vm.hardware_section.nics,
|
250
|
+
networks)
|
251
|
+
@logger.debug("Updating agent env to: #{env.inspect}")
|
252
|
+
set_agent_env(vm, env)
|
253
|
+
|
254
|
+
@logger.debug("Powering #{vapp.name} back on.")
|
255
|
+
@client.power_on_vapp(vapp)
|
256
|
+
@logger.info("Configured vApp networks: #{vapp}")
|
257
|
+
end
|
258
|
+
end
|
259
|
+
rescue VCloudSdk::CloudError => e
|
260
|
+
log_exception("configure vApp networks: #{vapp}", e)
|
261
|
+
raise e
|
262
|
+
end
|
263
|
+
|
264
|
+
def attach_disk(vapp_id, disk_id)
|
265
|
+
@client = client
|
266
|
+
|
267
|
+
with_thread_name("attach_disk(#{vapp_id} #{disk_id})") do
|
268
|
+
Util.retry_operation("attach_disk(#{vapp_id}, #{disk_id})",
|
269
|
+
@retries['cpi'], @control['backoff']) do
|
270
|
+
@logger.info("Attaching disk: #{disk_id} on vm: #{vapp_id}")
|
271
|
+
|
272
|
+
vapp, vm = get_vapp_vm_by_vapp_id(vapp_id)
|
273
|
+
# vm.hardware_section will change, save current state of disks
|
274
|
+
disks_previous = Array.new(vm.hardware_section.hard_disks)
|
275
|
+
|
276
|
+
disk = @client.get_disk(disk_id)
|
277
|
+
@client.attach_disk(disk, vm)
|
278
|
+
|
279
|
+
vapp, vm = get_vapp_vm_by_vapp_id(vapp_id)
|
280
|
+
persistent_disk = get_newly_added_disk(vm, disks_previous)
|
281
|
+
|
282
|
+
env = get_current_agent_env(vm)
|
283
|
+
env["disks"]["persistent"][disk_id] = persistent_disk.disk_id
|
284
|
+
@logger.info("Updating agent env to: #{env.inspect}")
|
285
|
+
set_agent_env(vm, env)
|
286
|
+
|
287
|
+
@logger.info("Attached disk:#{disk_id} to VM:#{vapp_id}")
|
288
|
+
end
|
289
|
+
end
|
290
|
+
rescue VCloudSdk::CloudError => e
|
291
|
+
log_exception("attach disk", e)
|
292
|
+
raise e
|
293
|
+
end
|
294
|
+
|
295
|
+
def detach_disk(vapp_id, disk_id)
|
296
|
+
@client = client
|
297
|
+
|
298
|
+
with_thread_name("detach_disk(#{vapp_id} #{disk_id})") do
|
299
|
+
Util.retry_operation("detach_disk(#{vapp_id}, #{disk_id})",
|
300
|
+
@retries['cpi'], @control['backoff']) do
|
301
|
+
@logger.info("Detaching disk: #{disk_id} from vm: #{vapp_id}")
|
302
|
+
|
303
|
+
vapp, vm = get_vapp_vm_by_vapp_id(vapp_id)
|
304
|
+
|
305
|
+
disk = @client.get_disk(disk_id)
|
306
|
+
begin
|
307
|
+
@client.detach_disk(disk, vm)
|
308
|
+
rescue VCloudSdk::VmSuspendedError => e
|
309
|
+
@client.discard_suspended_state_vapp(vapp)
|
310
|
+
@client.detach_disk(disk, vm)
|
311
|
+
end
|
312
|
+
|
313
|
+
env = get_current_agent_env(vm)
|
314
|
+
env["disks"]["persistent"].delete(disk_id)
|
315
|
+
@logger.info("Updating agent env to: #{env.inspect}")
|
316
|
+
set_agent_env(vm, env)
|
317
|
+
|
318
|
+
@logger.info("Detached disk: #{disk_id} on vm: #{vapp_id}")
|
319
|
+
end
|
320
|
+
end
|
321
|
+
rescue VCloudSdk::CloudError => e
|
322
|
+
log_exception("detach disk", e)
|
323
|
+
raise e
|
324
|
+
end
|
325
|
+
|
326
|
+
def create_disk(size_mb, vm_locality = nil)
|
327
|
+
@client = client
|
328
|
+
|
329
|
+
with_thread_name("create_disk(#{size_mb}, vm_locality)") do
|
330
|
+
Util.retry_operation("create_disk(#{size_mb}, vm_locality)",
|
331
|
+
@retries['cpi'], @control['backoff']) do
|
332
|
+
@logger.info("Create disk: #{size_mb}, #{vm_locality}")
|
333
|
+
disk_name = "#{generate_unique_name}"
|
334
|
+
disk = nil
|
335
|
+
if vm_locality.nil?
|
336
|
+
@logger.info("Creating disk: #{disk_name} #{size_mb}")
|
337
|
+
disk = @client.create_disk(disk_name, size_mb)
|
338
|
+
else
|
339
|
+
# vm_locality => vapp_id
|
340
|
+
vapp, vm = get_vapp_vm_by_vapp_id(vm_locality)
|
341
|
+
@logger.info("Creating disk: #{disk_name} #{size_mb} #{vm.name}")
|
342
|
+
disk = @client.create_disk(disk_name, size_mb, vm)
|
343
|
+
end
|
344
|
+
@logger.info("Created disk: #{disk_name} #{disk.urn} #{size_mb} " +
|
345
|
+
"#{vm_locality}")
|
346
|
+
disk.urn
|
347
|
+
end
|
348
|
+
end
|
349
|
+
rescue VCloudSdk::CloudError => e
|
350
|
+
log_exception("create disk", e)
|
351
|
+
raise e
|
352
|
+
end
|
353
|
+
|
354
|
+
def delete_disk(disk_id)
|
355
|
+
@client = client
|
356
|
+
|
357
|
+
with_thread_name("delete_disk(#{disk_id})") do
|
358
|
+
Util.retry_operation("delete_disk(#{disk_id})", @retries['cpi'],
|
359
|
+
@control['backoff']) do
|
360
|
+
@logger.info("Deleting disk: #{disk_id}")
|
361
|
+
disk = @client.get_disk(disk_id)
|
362
|
+
@client.delete_disk(disk)
|
363
|
+
@logger.info("Deleted disk: #{disk_id}")
|
364
|
+
end
|
365
|
+
end
|
366
|
+
rescue VCloudSdk::CloudError => e
|
367
|
+
log_exception("delete disk", e)
|
368
|
+
raise e
|
369
|
+
end
|
370
|
+
|
371
|
+
def get_disk_size_mb(disk_id)
|
372
|
+
@client = client
|
373
|
+
|
374
|
+
with_thread_name("get_disk_size(#{disk_id})") do
|
375
|
+
Util.retry_operation("get_disk_size(#{disk_id})", @retries['cpi'],
|
376
|
+
@control['backoff']) do
|
377
|
+
@logger.info("Getting disk size: #{disk_id}")
|
378
|
+
disk = @client.get_disk(disk_id)
|
379
|
+
@logger.info("Disk #{disk_id} size: #{disk.size_mb} MB")
|
380
|
+
disk.size_mb
|
381
|
+
end
|
382
|
+
end
|
383
|
+
rescue VCloudSdk::CloudError => e
|
384
|
+
log_exception("get_disk_size", e)
|
385
|
+
raise e
|
386
|
+
end
|
387
|
+
|
388
|
+
def validate_deployment(old_manifest, new_manifest)
|
389
|
+
# There is TODO in vSphere CPI that questions the necessity of this method
|
390
|
+
raise NotImplementedError, "validate_deployment"
|
391
|
+
end
|
392
|
+
|
393
|
+
private
|
394
|
+
|
395
|
+
def finalize_options
|
396
|
+
@vcd['control'] = {} unless @vcd['control']
|
397
|
+
@vcd['control']['retries'] = {} unless @vcd['control']['retries']
|
398
|
+
@vcd['control']['retries']['default'] ||= RETRIES_DEFAULT
|
399
|
+
@vcd['control']['retries']['upload_vapp_files'] ||=
|
400
|
+
RETRIES_UPLOAD_VAPP_FILES
|
401
|
+
@vcd['control']['retries']['cpi'] ||= RETRIES_CPI
|
402
|
+
@vcd['control']['delay'] ||= DELAY
|
403
|
+
@vcd['control']['time_limit_sec'] = {} unless
|
404
|
+
@vcd['control']['time_limit_sec']
|
405
|
+
@vcd['control']['time_limit_sec']['default'] ||= TIMELIMIT_DEFAULT
|
406
|
+
@vcd['control']['time_limit_sec']['delete_vapp_template'] ||=
|
407
|
+
TIMELIMIT_DELETE_VAPP_TEMPLATE
|
408
|
+
@vcd['control']['time_limit_sec']['delete_vapp'] ||= TIMELIMIT_DELETE_VAPP
|
409
|
+
@vcd['control']['time_limit_sec']['delete_media'] ||=
|
410
|
+
TIMELIMIT_DELETE_MEDIA
|
411
|
+
@vcd['control']['time_limit_sec']['instantiate_vapp_template'] ||=
|
412
|
+
TIMELIMIT_INSTANTIATE_VAPP_TEMPLATE
|
413
|
+
@vcd['control']['time_limit_sec']['power_on'] ||= TIMELIMIT_POWER_ON
|
414
|
+
@vcd['control']['time_limit_sec']['power_off'] ||= TIMELIMIT_POWER_OFF
|
415
|
+
@vcd['control']['time_limit_sec']['undeploy'] ||= TIMELIMIT_UNDEPLOY
|
416
|
+
@vcd['control']['time_limit_sec']['process_descriptor_vapp_template'] ||=
|
417
|
+
TIMELIMIT_PROCESS_DESCRIPTOR_VAPP_TEMPLATE
|
418
|
+
@vcd['control']['time_limit_sec']['http_request'] ||=
|
419
|
+
TIMELIMIT_HTTP_REQUEST
|
420
|
+
@vcd['control']['backoff'] ||= BACKOFF
|
421
|
+
@vcd['control']['rest_throttle'] = {} unless
|
422
|
+
@vcd['control']['rest_throttle']
|
423
|
+
@vcd['control']['rest_throttle']['min'] ||= REST_THROTTLE_MIN
|
424
|
+
@vcd['control']['rest_throttle']['max'] ||= REST_THROTTLE_MAX
|
425
|
+
@vcd['debug'] = {} unless @vcd['debug']
|
426
|
+
@vcd['debug']['delete_vapp'] = DEBUG_DELETE_VAPP unless
|
427
|
+
@vcd['debug']['delete_vapp']
|
428
|
+
end
|
429
|
+
|
430
|
+
def create_client()
|
431
|
+
url = @vcd["url"]
|
432
|
+
@logger.debug("Create session to VCD cloud: #{url}")
|
433
|
+
#@rest_logger.debug("Session to VCD cloud: #{url}")
|
434
|
+
|
435
|
+
@client = VCloudSdk::Client.new(url, @vcd["user"],
|
436
|
+
@vcd["password"], @vcd["entities"], @vcd["control"])
|
437
|
+
|
438
|
+
@logger.info("Created session to VCD cloud: #{url}")
|
439
|
+
|
440
|
+
@client
|
441
|
+
rescue VCloudSdk::ApiError => e
|
442
|
+
log_exception(e, "Failed to connect and establish session.")
|
443
|
+
raise e
|
444
|
+
end
|
445
|
+
|
446
|
+
def destroy_client()
|
447
|
+
url = @vcd["url"]
|
448
|
+
@logger.debug("Destroy session to VCD cloud: #{url}")
|
449
|
+
# TODO VCloudSdk::Client should permit logout.
|
450
|
+
@logger.info("Destroyed session to VCD cloud: #{url}")
|
451
|
+
end
|
452
|
+
|
453
|
+
def generate_unique_name
|
454
|
+
UUIDTools::UUID.random_create.to_s
|
455
|
+
end
|
456
|
+
|
457
|
+
def log_exception(op, e)
|
458
|
+
@logger.error("Failed to #{op}.")
|
459
|
+
@logger.error(e)
|
460
|
+
end
|
461
|
+
|
462
|
+
def generate_network_env(nics, networks)
|
463
|
+
nic_net = {}
|
464
|
+
nics.each do |nic|
|
465
|
+
nic_net[nic.network] = nic
|
466
|
+
end
|
467
|
+
@logger.debug("nic_net #{nic_net.inspect}")
|
468
|
+
|
469
|
+
network_env = {}
|
470
|
+
networks.each do |network_name, network|
|
471
|
+
network_entry = network.dup
|
472
|
+
v_network_name = network["cloud_properties"]["name"]
|
473
|
+
nic = nic_net[v_network_name]
|
474
|
+
if nic.nil? then
|
475
|
+
@logger.warn("Not generating network env for #{v_network_name}")
|
476
|
+
next
|
477
|
+
end
|
478
|
+
network_entry["mac"] = nic.mac_address
|
479
|
+
network_env[network_name] = network_entry
|
480
|
+
end
|
481
|
+
network_env
|
482
|
+
end
|
483
|
+
|
484
|
+
def generate_disk_env(system_disk, ephemeral_disk)
|
485
|
+
{
|
486
|
+
"system" => system_disk.disk_id,
|
487
|
+
"ephemeral" => ephemeral_disk.disk_id,
|
488
|
+
"persistent" => {}
|
489
|
+
}
|
490
|
+
end
|
491
|
+
|
492
|
+
def generate_agent_env(name, vm, agent_id, networking_env, disk_env)
|
493
|
+
vm_env = {
|
494
|
+
"name" => name,
|
495
|
+
"id" => vm.urn
|
496
|
+
}
|
497
|
+
|
498
|
+
env = {}
|
499
|
+
env["vm"] = vm_env
|
500
|
+
env["agent_id"] = agent_id
|
501
|
+
env["networks"] = networking_env
|
502
|
+
env["disks"] = disk_env
|
503
|
+
env.merge!(@agent_properties)
|
504
|
+
end
|
505
|
+
|
506
|
+
def get_current_agent_env(vm)
|
507
|
+
env = @client.get_metadata(vm, @vcd["entities"]["vm_metadata_key"])
|
508
|
+
@logger.info("Current agent env: #{env.inspect}")
|
509
|
+
Yajl::Parser.parse(env)
|
510
|
+
end
|
511
|
+
|
512
|
+
def set_agent_env(vm, env)
|
513
|
+
env_json = Yajl::Encoder.encode(env)
|
514
|
+
@logger.debug("env.iso content #{env_json}")
|
515
|
+
|
516
|
+
begin
|
517
|
+
# Clear existing ISO if one exists.
|
518
|
+
@logger.info("Ejecting ISO #{vm.name}")
|
519
|
+
@client.eject_catalog_media(vm, vm.name)
|
520
|
+
@logger.info("Deleting ISO #{vm.name}")
|
521
|
+
@client.delete_catalog_media(vm.name)
|
522
|
+
rescue VCloudSdk::ObjectNotFoundError
|
523
|
+
@logger.debug("No ISO to eject/delete before setting new agent env.")
|
524
|
+
# Continue setting agent env...
|
525
|
+
end
|
526
|
+
|
527
|
+
# generate env iso, and insert into VM
|
528
|
+
Dir.mktmpdir do |path|
|
529
|
+
env_path = File.join(path, "env")
|
530
|
+
iso_path = File.join(path, "env.iso")
|
531
|
+
File.open(env_path, "w") { |f| f.write(env_json) }
|
532
|
+
output = `genisoimage -o #{iso_path} #{env_path} 2>&1`
|
533
|
+
raise "#{$?.exitstatus} -#{output}" if $?.exitstatus != 0
|
534
|
+
|
535
|
+
@client.set_metadata(vm, @vcd["entities"]["vm_metadata_key"], env_json)
|
536
|
+
|
537
|
+
storage_profiles = @client.get_ovdc.storage_profiles || []
|
538
|
+
media_storage_profile = storage_profiles.find { |sp| sp['name'] ==
|
539
|
+
@vcd["entities"]["media_storage_profile"] }
|
540
|
+
@logger.info("Uploading and inserting ISO #{iso_path} as #{vm.name} " +
|
541
|
+
"to #{media_storage_profile.inspect}")
|
542
|
+
@client.upload_catalog_media(vm.name, iso_path, media_storage_profile)
|
543
|
+
@client.insert_catalog_media(vm, vm.name)
|
544
|
+
@logger.info("Uploaded and inserted ISO #{iso_path} as #{vm.name}")
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
def delete_vapp_networks(vapp, exclude_nets)
|
549
|
+
exclude = exclude_nets.map {|k,v| v["cloud_properties"]["name"]}.uniq
|
550
|
+
@client.delete_networks(vapp, exclude)
|
551
|
+
@logger.debug("Deleted vApp #{vapp.name} networks excluding " +
|
552
|
+
"#{exclude.inspect}.")
|
553
|
+
end
|
554
|
+
|
555
|
+
def add_vapp_networks(vapp, networks)
|
556
|
+
@logger.debug("Networks to add: #{networks.inspect}")
|
557
|
+
ovdc = @client.get_ovdc
|
558
|
+
accessible_org_networks = ovdc.available_networks
|
559
|
+
@logger.debug("Accessible Org nets: #{accessible_org_networks.inspect}")
|
560
|
+
|
561
|
+
cloud_networks = networks.map { |k,v| v["cloud_properties"]["name"] }.uniq
|
562
|
+
cloud_networks.each do |configured_network|
|
563
|
+
@logger.debug("Adding configured network: #{configured_network}")
|
564
|
+
org_net = accessible_org_networks.find {
|
565
|
+
|n| n['name'] == configured_network }
|
566
|
+
unless org_net
|
567
|
+
raise VCloudSdk::CloudError, "Configured network: " +
|
568
|
+
"#{configured_network}, is not accessible to VDC:#{ovdc.name}."
|
569
|
+
end
|
570
|
+
@logger.debug("Adding configured network: #{configured_network}, => " +
|
571
|
+
"Org net:#{org_net.inspect} to vApp:#{vapp.name}.")
|
572
|
+
@client.add_network(vapp, org_net)
|
573
|
+
@logger.debug("Added vApp network: #{configured_network}.")
|
574
|
+
end
|
575
|
+
@logger.debug("Accessible configured networks added:#{networks.inspect}.")
|
576
|
+
end
|
577
|
+
|
578
|
+
def add_vm_nics(v, networks)
|
579
|
+
networks.values.each_with_index do |network, nic_index|
|
580
|
+
if nic_index + 1 >= VM_NIC_LIMIT then
|
581
|
+
@logger.warn("Max number of NICs reached")
|
582
|
+
break
|
583
|
+
end
|
584
|
+
configured_network = network["cloud_properties"]["name"]
|
585
|
+
@logger.info("Adding NIC with IP address #{network["ip"]}.")
|
586
|
+
v.add_nic(nic_index, configured_network,
|
587
|
+
VCloudSdk::Xml::IP_ADDRESSING_MODE[:MANUAL], network["ip"])
|
588
|
+
v.connect_nic(nic_index, configured_network,
|
589
|
+
VCloudSdk::Xml::IP_ADDRESSING_MODE[:MANUAL], network["ip"])
|
590
|
+
end
|
591
|
+
@logger.info("NICs added to #{v.name} and connected to network:" +
|
592
|
+
" #{networks.inspect}")
|
593
|
+
end
|
594
|
+
|
595
|
+
def get_vm(vapp)
|
596
|
+
vms = vapp.vms
|
597
|
+
raise IndexError, "Invalid number of vApp VMs" unless vms.size == 1
|
598
|
+
vms[0]
|
599
|
+
end
|
600
|
+
|
601
|
+
def get_vapp_vm_by_vapp_id(id)
|
602
|
+
vapp = @client.get_vapp(id)
|
603
|
+
[vapp, get_vm(vapp)]
|
604
|
+
end
|
605
|
+
|
606
|
+
def get_newly_added_disk(vm, disks_previous)
|
607
|
+
disks_current = vm.hardware_section.hard_disks
|
608
|
+
newly_added = disks_current - disks_previous
|
609
|
+
|
610
|
+
if newly_added.size != 1
|
611
|
+
@logger.debug("Previous disks in #{vapp_id}: #{disks_previous.inspect}")
|
612
|
+
@logger.debug("Current disks in #{vapp_id}: #{disks_current.inspect}")
|
613
|
+
raise IndexError, "Expecting #{disks_previous.size + 1} disks, found " +
|
614
|
+
"#{disks_current.size}"
|
615
|
+
end
|
616
|
+
|
617
|
+
@logger.info("Newly added disk: #{newly_added[0]}")
|
618
|
+
newly_added[0]
|
619
|
+
end
|
620
|
+
|
621
|
+
def independent_disks(disk_locality)
|
622
|
+
disk_locality ||= []
|
623
|
+
@logger.info("Instantiate vApp accessible to disks: " +
|
624
|
+
"#{disk_locality.join(',')}")
|
625
|
+
disks = []
|
626
|
+
disk_locality.each do |disk_id|
|
627
|
+
disks << @client.get_disk(disk_id)
|
628
|
+
end
|
629
|
+
disks
|
630
|
+
end
|
631
|
+
|
632
|
+
end
|
633
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module VCloudCloud
|
2
|
+
|
3
|
+
VM_NIC_LIMIT = 10
|
4
|
+
|
5
|
+
# control options
|
6
|
+
RETRIES_DEFAULT = 5
|
7
|
+
RETRIES_UPLOAD_VAPP_FILES = 7
|
8
|
+
RETRIES_CPI = 3
|
9
|
+
DELAY = 1
|
10
|
+
TIMELIMIT_DEFAULT = 1200
|
11
|
+
TIMELIMIT_DELETE_VAPP_TEMPLATE = 1200
|
12
|
+
TIMELIMIT_DELETE_VAPP = 1200
|
13
|
+
TIMELIMIT_DELETE_MEDIA = 1200
|
14
|
+
TIMELIMIT_INSTANTIATE_VAPP_TEMPLATE = 3000
|
15
|
+
TIMELIMIT_POWER_ON = 6000
|
16
|
+
TIMELIMIT_POWER_OFF = 6000
|
17
|
+
TIMELIMIT_UNDEPLOY = 7200
|
18
|
+
TIMELIMIT_PROCESS_DESCRIPTOR_VAPP_TEMPLATE = 3000
|
19
|
+
TIMELIMIT_HTTP_REQUEST = 240
|
20
|
+
BACKOFF = 2
|
21
|
+
REST_THROTTLE_MIN = 0
|
22
|
+
REST_THROTTLE_MAX = 1
|
23
|
+
|
24
|
+
# debug option
|
25
|
+
DEBUG_DELETE_VAPP = true
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module VCloudCloud
|
2
|
+
class Util
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def retry_operation(op, attempts, backoff, &b)
|
6
|
+
attempts.times do |attempt|
|
7
|
+
begin
|
8
|
+
return b.call
|
9
|
+
rescue VCloudSdk::ApiError => e
|
10
|
+
raise e if attempt >= attempts-1
|
11
|
+
delay = backoff ** attempt
|
12
|
+
Config.logger.error("Retry-attempt #{attempt+1}/#{attempts} " +
|
13
|
+
"failed to #{op}, retrying in #{delay} seconds.\t#{e}")
|
14
|
+
sleep (delay)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|