bosh-director 1.3048.0 → 1.3050.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bosh/director/api/controllers/packages_controller.rb +19 -1
- data/lib/bosh/director/api/controllers/releases_controller.rb +1 -2
- data/lib/bosh/director/api/route_configuration.rb +0 -4
- data/lib/bosh/director/api.rb +0 -1
- data/lib/bosh/director/deployment_plan/planner_factory.rb +41 -27
- data/lib/bosh/director/errors.rb +1 -0
- data/lib/bosh/director/jobs/delete_release.rb +4 -4
- data/lib/bosh/director/jobs/export_release.rb +53 -5
- data/lib/bosh/director/jobs/update_release.rb +132 -82
- data/lib/bosh/director/version.rb +1 -1
- data/lib/bosh/director.rb +0 -1
- data/lib/cloud/dummy.rb +39 -3
- metadata +23 -31
- data/lib/bosh/director/api/compiled_package_group_manager.rb +0 -17
- data/lib/bosh/director/api/controllers/compiled_packages_controller.rb +0 -63
- data/lib/bosh/director/compiled_package/compiled_package_inserter.rb +0 -45
- data/lib/bosh/director/compiled_package/compiled_packages_export.rb +0 -35
- data/lib/bosh/director/compiled_package_downloader.rb +0 -33
- data/lib/bosh/director/compiled_package_manifest.rb +0 -31
- data/lib/bosh/director/compiled_packages_exporter.rb +0 -24
- data/lib/bosh/director/jobs/import_compiled_packages.rb +0 -44
@@ -32,14 +32,10 @@ module Bosh::Director
|
|
32
32
|
@release_version_model = nil
|
33
33
|
|
34
34
|
@rebase = !!options['rebase']
|
35
|
-
@skip_if_exists = !!options['skip_if_exists']
|
36
35
|
|
37
36
|
@manifest = nil
|
38
37
|
@name = nil
|
39
38
|
@version = nil
|
40
|
-
|
41
|
-
@packages_unchanged = false
|
42
|
-
@jobs_unchanged = false
|
43
39
|
end
|
44
40
|
|
45
41
|
# Extracts release tarball, verifies release manifest and saves release in DB
|
@@ -81,9 +77,7 @@ module Bosh::Director
|
|
81
77
|
|
82
78
|
result = Bosh::Exec.sh("tar -C #{release_dir} -xzf #{@release_path} 2>&1", :on_error => :return)
|
83
79
|
if result.failed?
|
84
|
-
logger.error("Failed to extract release archive '#{@release_path}' into dir '#{release_dir}', "
|
85
|
-
"tar returned #{result.exit_status}, " +
|
86
|
-
"output: #{result.output}")
|
80
|
+
logger.error("Failed to extract release archive '#{@release_path}' into dir '#{release_dir}', tar returned #{result.exit_status}, output: #{result.output})")
|
87
81
|
FileUtils.rm_rf(release_dir)
|
88
82
|
raise ReleaseInvalidArchive, "Extracting release archive failed. Check task debug log for details."
|
89
83
|
end
|
@@ -95,19 +89,13 @@ module Bosh::Director
|
|
95
89
|
# @return [void]
|
96
90
|
def verify_manifest(release_dir)
|
97
91
|
manifest_file = File.join(release_dir, "release.MF")
|
98
|
-
unless File.file?(manifest_file)
|
99
|
-
raise ReleaseManifestNotFound, "Release manifest not found"
|
100
|
-
end
|
92
|
+
raise ReleaseManifestNotFound, "Release manifest not found" unless File.file?(manifest_file)
|
101
93
|
|
102
94
|
@manifest = Psych.load_file(manifest_file)
|
103
95
|
|
104
96
|
#handle compiled_release case
|
105
97
|
@compiled_release = !!@manifest["compiled_packages"]
|
106
|
-
|
107
|
-
@packages_folder = "compiled_packages"
|
108
|
-
else
|
109
|
-
@packages_folder = "packages"
|
110
|
-
end
|
98
|
+
@packages_folder = @compiled_release ? "compiled_packages" : "packages"
|
111
99
|
|
112
100
|
normalize_manifest
|
113
101
|
|
@@ -126,6 +114,15 @@ module Bosh::Director
|
|
126
114
|
@uncommitted_changes = @manifest.fetch("uncommitted_changes", nil)
|
127
115
|
end
|
128
116
|
|
117
|
+
def compiled_release
|
118
|
+
raise "Don't know what kind of release we have until verify_release is called" unless @manifest
|
119
|
+
@compiled_release
|
120
|
+
end
|
121
|
+
|
122
|
+
def source_release
|
123
|
+
!compiled_release
|
124
|
+
end
|
125
|
+
|
129
126
|
# Processes uploaded release, creates jobs and packages in DB if needed
|
130
127
|
# @param [String] release_dir local path to the unpacked release
|
131
128
|
# @return [void]
|
@@ -136,29 +133,14 @@ module Bosh::Director
|
|
136
133
|
@version = next_release_version
|
137
134
|
end
|
138
135
|
|
139
|
-
version_attrs = {
|
140
|
-
:release => @release_model,
|
141
|
-
:version => @version.to_s
|
142
|
-
}
|
136
|
+
version_attrs = { :release => @release_model, :version => @version.to_s }
|
143
137
|
version_attrs[:uncommitted_changes] = @uncommitted_changes if @uncommitted_changes
|
144
138
|
version_attrs[:commit_hash] = @commit_hash if @commit_hash
|
145
139
|
|
146
|
-
@
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
"Release version invalid `#{@name}/#{@version}'"
|
151
|
-
elsif @skip_if_exists
|
152
|
-
event_log.begin_stage("Release already exists", 1)
|
153
|
-
event_log.track("#{@name}/#{@version}") {}
|
154
|
-
return
|
155
|
-
else
|
156
|
-
raise ReleaseAlreadyExists,
|
157
|
-
"Release `#{@name}/#{@version}' already exists"
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
@release_version_model.save
|
140
|
+
@release_is_new = false
|
141
|
+
@release_version_model = Models::ReleaseVersion.find_or_create(version_attrs) {
|
142
|
+
@release_is_new = true
|
143
|
+
}
|
162
144
|
|
163
145
|
single_step_stage("Resolving package dependencies") do
|
164
146
|
resolve_package_dependencies(@manifest[@packages_folder])
|
@@ -166,7 +148,7 @@ module Bosh::Director
|
|
166
148
|
|
167
149
|
@packages = {}
|
168
150
|
process_packages(release_dir)
|
169
|
-
process_jobs(release_dir)
|
151
|
+
process_jobs(release_dir) if @release_is_new
|
170
152
|
|
171
153
|
event_log.begin_stage(@compiled_release ? "Compiled Release has been created" : "Release has been created", 1)
|
172
154
|
event_log.track("#{@name}/#{@version}") {}
|
@@ -215,12 +197,16 @@ module Bosh::Director
|
|
215
197
|
|
216
198
|
new_packages = []
|
217
199
|
existing_packages = []
|
200
|
+
registered_packages = []
|
218
201
|
|
219
202
|
@manifest[@packages_folder].each do |package_meta|
|
220
203
|
# Checking whether we might have the same bits somewhere
|
221
|
-
packages = Models::Package.where(fingerprint: package_meta["fingerprint"]).all
|
222
204
|
|
205
|
+
packages = Models::Package.where(fingerprint: package_meta["fingerprint"]).all
|
223
206
|
if packages.empty?
|
207
|
+
unless @release_is_new
|
208
|
+
raise ReleaseInvalidPackage, "package #{package_meta['name']}/#{package_meta['version']} not part of previous upload of release #{@name}/#{@version}"
|
209
|
+
end
|
224
210
|
new_packages << package_meta
|
225
211
|
next
|
226
212
|
end
|
@@ -240,7 +226,11 @@ module Bosh::Director
|
|
240
226
|
existing_package.save
|
241
227
|
end
|
242
228
|
|
243
|
-
|
229
|
+
if existing_package.release_versions.include? @release_version_model
|
230
|
+
registered_packages << [existing_package, package_meta]
|
231
|
+
else
|
232
|
+
existing_packages << [existing_package, package_meta]
|
233
|
+
end
|
244
234
|
else
|
245
235
|
# We found a package with the same fingerprint but different
|
246
236
|
# (release, name, version) tuple, so we need to make a copy
|
@@ -252,17 +242,55 @@ module Bosh::Director
|
|
252
242
|
end
|
253
243
|
end
|
254
244
|
|
255
|
-
|
256
|
-
|
257
|
-
|
245
|
+
did_something = false
|
246
|
+
|
247
|
+
package_stemcell_hashes1, created_package = create_packages(new_packages, release_dir)
|
248
|
+
did_something |= created_package
|
249
|
+
|
250
|
+
package_stemcell_hashes2, modified_package = use_existing_packages(existing_packages, release_dir)
|
251
|
+
did_something |= modified_package
|
258
252
|
|
259
|
-
|
253
|
+
if @compiled_release
|
254
|
+
compatible_stemcell_combos = registered_packages.flat_map do |pkg, pkg_meta|
|
255
|
+
stemcells_used_by_package(pkg_meta).map do |stemcell|
|
256
|
+
{
|
257
|
+
package: pkg,
|
258
|
+
stemcell: stemcell
|
259
|
+
}
|
260
|
+
end
|
261
|
+
end
|
262
|
+
consolidated_package_stemcell_hashes = Array(package_stemcell_hashes1) | Array(package_stemcell_hashes2) | compatible_stemcell_combos
|
263
|
+
did_something |= create_compiled_packages(consolidated_package_stemcell_hashes, release_dir)
|
264
|
+
else
|
265
|
+
did_something |= backfill_source_for_packages(registered_packages, release_dir)
|
266
|
+
end
|
267
|
+
did_something
|
268
|
+
end
|
269
|
+
|
270
|
+
# @return [boolean] true if sources were added to at least one package; false if the call had no effect.
|
271
|
+
def backfill_source_for_packages(packages, release_dir)
|
272
|
+
return false if packages.empty?
|
273
|
+
|
274
|
+
had_effect = false
|
275
|
+
single_step_stage("Processing #{packages.size} existing package#{"s" if packages.size > 1}") do
|
276
|
+
packages.each do |package, package_meta|
|
277
|
+
package_desc = "#{package.name}/#{package.version}"
|
278
|
+
logger.info("Adding source for package `#{package_desc}'")
|
279
|
+
had_effect |= save_package_source_blob(package, package_meta, release_dir)
|
280
|
+
package.save
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
had_effect
|
260
285
|
end
|
261
286
|
|
262
287
|
# Points release DB model to existing packages described by given metadata
|
263
|
-
# @param [Array<Array>] packages Existing packages metadata
|
264
|
-
|
265
|
-
|
288
|
+
# @param [Array<Array>] packages Existing packages metadata.
|
289
|
+
# @return [Array<Hash>] package & stemcell matching pairs that were registered. empty if no packages were changed.
|
290
|
+
def use_existing_packages(packages, release_dir)
|
291
|
+
if packages.empty?
|
292
|
+
return [], false
|
293
|
+
end
|
266
294
|
|
267
295
|
package_stemcell_hashes = []
|
268
296
|
|
@@ -272,63 +300,69 @@ module Bosh::Director
|
|
272
300
|
logger.info("Using existing package `#{package_desc}'")
|
273
301
|
register_package(package)
|
274
302
|
|
275
|
-
if
|
303
|
+
if compiled_release
|
276
304
|
stemcells = stemcells_used_by_package(package_meta)
|
277
305
|
stemcells.each do |stemcell|
|
278
|
-
hash = {
|
306
|
+
hash = { package: package, stemcell: stemcell}
|
279
307
|
package_stemcell_hashes << hash
|
280
308
|
end
|
281
309
|
end
|
310
|
+
|
311
|
+
if source_release && package.blobstore_id.nil?
|
312
|
+
save_package_source_blob(package, package_meta, release_dir)
|
313
|
+
package.save
|
314
|
+
end
|
282
315
|
end
|
283
316
|
end
|
284
317
|
|
285
|
-
package_stemcell_hashes
|
318
|
+
return package_stemcell_hashes, true
|
286
319
|
end
|
287
320
|
|
288
321
|
# Creates packages using provided metadata
|
289
322
|
# @param [Array<Hash>] packages Packages metadata
|
290
323
|
# @param [String] release_dir local path to the unpacked release
|
291
|
-
# @return [Array<Hash
|
324
|
+
# @return [Array<Hash>, boolean] array of compiled package & stemcell matching pairs that were registered, and a
|
325
|
+
# flag indicating if any changes were made to the database.
|
292
326
|
def create_packages(package_metas, release_dir)
|
293
327
|
if package_metas.empty?
|
294
|
-
|
295
|
-
return
|
328
|
+
return [], false
|
296
329
|
end
|
297
330
|
|
298
331
|
package_stemcell_hashes = []
|
332
|
+
|
299
333
|
event_log.begin_stage("Creating new packages", package_metas.size)
|
300
334
|
|
301
|
-
|
335
|
+
package_metas.each do |package_meta|
|
302
336
|
package_desc = "#{package_meta["name"]}/#{package_meta["version"]}"
|
337
|
+
package = nil
|
303
338
|
event_log.track(package_desc) do
|
304
339
|
logger.info("Creating new package `#{package_desc}'")
|
305
340
|
package = create_package(package_meta, release_dir)
|
306
341
|
register_package(package)
|
307
|
-
package
|
308
342
|
end
|
309
343
|
|
310
344
|
if @compiled_release
|
311
345
|
stemcells = stemcells_used_by_package(package_meta)
|
312
346
|
stemcells.each do |stemcell|
|
313
|
-
hash = {
|
347
|
+
hash = { package: package, stemcell: stemcell}
|
314
348
|
package_stemcell_hashes << hash
|
315
349
|
end
|
316
350
|
end
|
317
351
|
end
|
318
352
|
|
319
|
-
package_stemcell_hashes
|
353
|
+
return package_stemcell_hashes, true
|
320
354
|
end
|
321
355
|
|
356
|
+
# @return [boolean] true if at least one job was created; false if the call had no effect.
|
322
357
|
def create_compiled_packages(all_compiled_packages, release_dir)
|
323
|
-
if all_compiled_packages.nil?
|
324
|
-
return
|
325
|
-
end
|
358
|
+
return false if all_compiled_packages.nil?
|
326
359
|
|
327
360
|
event_log.begin_stage('Creating new compiled packages', all_compiled_packages.size)
|
328
361
|
|
362
|
+
had_effect = false
|
329
363
|
all_compiled_packages.each do |compiled_package_spec|
|
330
|
-
package = compiled_package_spec[
|
331
|
-
stemcell = compiled_package_spec[
|
364
|
+
package = compiled_package_spec[:package]
|
365
|
+
stemcell = compiled_package_spec[:stemcell]
|
332
366
|
|
333
367
|
existing_compiled_package = Models::CompiledPackage.where(
|
334
368
|
:package_id => package.id,
|
@@ -338,9 +372,11 @@ module Bosh::Director
|
|
338
372
|
package_desc = "#{package.name}/#{package.version} for #{stemcell.name}/#{stemcell.version}"
|
339
373
|
event_log.track(package_desc) do
|
340
374
|
create_compiled_package(package, stemcell, release_dir)
|
375
|
+
had_effect = true
|
341
376
|
end
|
342
377
|
end
|
343
378
|
end
|
379
|
+
had_effect
|
344
380
|
end
|
345
381
|
|
346
382
|
def stemcells_used_by_package(package_meta)
|
@@ -392,7 +428,7 @@ module Bosh::Director
|
|
392
428
|
package_attrs = {
|
393
429
|
:release => @release_model,
|
394
430
|
:name => name,
|
395
|
-
:sha1 =>
|
431
|
+
:sha1 => nil,
|
396
432
|
:blobstore_id => nil,
|
397
433
|
:fingerprint => package_meta['fingerprint'],
|
398
434
|
:version => version
|
@@ -401,24 +437,34 @@ module Bosh::Director
|
|
401
437
|
package = Models::Package.new(package_attrs)
|
402
438
|
package.dependency_set = package_meta['dependencies']
|
403
439
|
|
404
|
-
unless @compiled_release
|
405
|
-
existing_blob = package_meta['blobstore_id']
|
406
|
-
desc = "package '#{name}/#{version}'"
|
440
|
+
save_package_source_blob(package, package_meta, release_dir) unless @compiled_release
|
407
441
|
|
408
|
-
|
409
|
-
|
410
|
-
package.blobstore_id = BlobUtil.copy_blob(existing_blob)
|
442
|
+
package.save
|
443
|
+
end
|
411
444
|
|
412
|
-
|
413
|
-
|
445
|
+
# @return [boolean] true if a new blob was created; false otherwise
|
446
|
+
def save_package_source_blob(package, package_meta, release_dir)
|
447
|
+
return false unless package.blobstore_id.nil?
|
414
448
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
449
|
+
name, version = package_meta['name'], package_meta['version']
|
450
|
+
existing_blob = package_meta['blobstore_id']
|
451
|
+
desc = "package '#{name}/#{version}'"
|
452
|
+
|
453
|
+
package.sha1 = package_meta['sha1']
|
454
|
+
|
455
|
+
if existing_blob
|
456
|
+
logger.info("Creating #{desc} from existing blob #{existing_blob}")
|
457
|
+
package.blobstore_id = BlobUtil.copy_blob(existing_blob)
|
458
|
+
|
459
|
+
elsif package
|
460
|
+
logger.info("Creating #{desc} from provided bits")
|
461
|
+
|
462
|
+
package_tgz = File.join(release_dir, 'packages', "#{name}.tgz")
|
463
|
+
validate_tgz(package_tgz, desc)
|
464
|
+
package.blobstore_id = BlobUtil.create_blob(package_tgz)
|
419
465
|
end
|
420
466
|
|
421
|
-
|
467
|
+
true
|
422
468
|
end
|
423
469
|
|
424
470
|
def validate_tgz(tgz, desc)
|
@@ -465,15 +511,15 @@ module Bosh::Director
|
|
465
511
|
end
|
466
512
|
end
|
467
513
|
|
468
|
-
create_jobs(new_jobs, release_dir)
|
469
|
-
use_existing_jobs(existing_jobs)
|
514
|
+
did_something = create_jobs(new_jobs, release_dir)
|
515
|
+
did_something |= use_existing_jobs(existing_jobs)
|
516
|
+
|
517
|
+
did_something
|
470
518
|
end
|
471
519
|
|
520
|
+
# @return [boolean] true if at least one job was created; false if the call had no effect.
|
472
521
|
def create_jobs(jobs, release_dir)
|
473
|
-
if jobs.empty?
|
474
|
-
@jobs_unchanged = true
|
475
|
-
return
|
476
|
-
end
|
522
|
+
return false if jobs.empty?
|
477
523
|
|
478
524
|
event_log.begin_stage("Creating new jobs", jobs.size)
|
479
525
|
jobs.each do |job_meta|
|
@@ -484,6 +530,8 @@ module Bosh::Director
|
|
484
530
|
register_template(template)
|
485
531
|
end
|
486
532
|
end
|
533
|
+
|
534
|
+
true
|
487
535
|
end
|
488
536
|
|
489
537
|
def create_job(job_meta, release_dir)
|
@@ -492,9 +540,9 @@ module Bosh::Director
|
|
492
540
|
end
|
493
541
|
|
494
542
|
# @param [Array<Array>] jobs Existing jobs metadata
|
495
|
-
# @return [
|
543
|
+
# @return [boolean] true if at least one job was tied to the release version; false if the call had no effect.
|
496
544
|
def use_existing_jobs(jobs)
|
497
|
-
return if jobs.empty?
|
545
|
+
return false if jobs.empty?
|
498
546
|
|
499
547
|
single_step_stage("Processing #{jobs.size} existing job#{"s" if jobs.size > 1}") do
|
500
548
|
jobs.each do |template, _|
|
@@ -503,6 +551,8 @@ module Bosh::Director
|
|
503
551
|
register_template(template)
|
504
552
|
end
|
505
553
|
end
|
554
|
+
|
555
|
+
true
|
506
556
|
end
|
507
557
|
|
508
558
|
# Marks job template model as being used by release version
|
data/lib/bosh/director.rb
CHANGED
@@ -151,7 +151,6 @@ require 'bosh/director/api/controllers/stemcells_controller'
|
|
151
151
|
require 'bosh/director/api/controllers/tasks_controller'
|
152
152
|
require 'bosh/director/api/controllers/task_controller'
|
153
153
|
require 'bosh/director/api/controllers/users_controller'
|
154
|
-
require 'bosh/director/api/controllers/compiled_packages_controller'
|
155
154
|
require 'bosh/director/api/controllers/cloud_configs_controller'
|
156
155
|
require 'bosh/director/api/controllers/locks_controller'
|
157
156
|
require 'bosh/director/api/route_configuration'
|
data/lib/cloud/dummy.rb
CHANGED
@@ -49,9 +49,20 @@ module Bosh
|
|
49
49
|
# rubocop:enable ParameterLists
|
50
50
|
@logger.info('Dummy: create_vm')
|
51
51
|
|
52
|
+
ips = []
|
52
53
|
cmd = commands.next_create_vm_cmd
|
53
54
|
|
54
|
-
|
55
|
+
if cmd.ip_address
|
56
|
+
# special case used by dynamic IP assignment tests: CPI always chooses its own IP
|
57
|
+
write_agent_default_network(agent_id, cmd.ip_address)
|
58
|
+
ips << { 'network' => 'cloud', 'ip' => cmd.ip_address }
|
59
|
+
else
|
60
|
+
networks.each do |network_name, network|
|
61
|
+
ips << { 'network' => network_name, 'ip' => network['ip'] }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
allocate_ips(ips)
|
55
66
|
|
56
67
|
write_agent_settings(agent_id, {
|
57
68
|
agent_id: agent_id,
|
@@ -67,7 +78,7 @@ module Bosh
|
|
67
78
|
agent_pid = spawn_agent_process(agent_id)
|
68
79
|
|
69
80
|
FileUtils.mkdir_p(@running_vms_dir)
|
70
|
-
File.write(vm_file(agent_pid), agent_id)
|
81
|
+
File.write(vm_file(agent_pid), JSON.dump("agent_id" => agent_id, "ips" => ips))
|
71
82
|
|
72
83
|
agent_pid.to_s
|
73
84
|
end
|
@@ -79,6 +90,7 @@ module Bosh
|
|
79
90
|
rescue Errno::ESRCH
|
80
91
|
# rubocop:enable HandleExceptions
|
81
92
|
ensure
|
93
|
+
free_ips(ips_for_vm_id(vm_name)) if has_vm?(vm_name)
|
82
94
|
FileUtils.rm_rf(File.join(@base_dir, 'running_vms', vm_name))
|
83
95
|
end
|
84
96
|
|
@@ -202,8 +214,32 @@ module Bosh
|
|
202
214
|
agent_pid
|
203
215
|
end
|
204
216
|
|
217
|
+
def allocate_ips(ips)
|
218
|
+
ips.each do |ip|
|
219
|
+
begin
|
220
|
+
network_dir = File.join(@base_dir, 'dummy_cpi_networks', ip['network'])
|
221
|
+
FileUtils.makedirs(network_dir)
|
222
|
+
open(File.join(network_dir, ip['ip']), File::WRONLY|File::CREAT|File::EXCL).close
|
223
|
+
rescue Errno::EEXIST
|
224
|
+
# at this point we should actually free all the IPs we successfully allocated before the collision,
|
225
|
+
# but in practice the tests only feed in one IP per VM so that cleanup code would never be exercised
|
226
|
+
raise "IP Address #{ip['ip']} in network '#{ip['network']}' is already in use"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def free_ips(ips)
|
232
|
+
ips.each do |ip|
|
233
|
+
FileUtils.rm_rf(File.join(@base_dir, 'dummy_cpi_networks', ip['network'], ip['ip']))
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def ips_for_vm_id(vm_id)
|
238
|
+
JSON.parse(File.read(vm_file(vm_id)))['ips']
|
239
|
+
end
|
240
|
+
|
205
241
|
def agent_id_for_vm_id(vm_id)
|
206
|
-
File.read(vm_file(vm_id))
|
242
|
+
JSON.parse(File.read(vm_file(vm_id)))['agent_id']
|
207
243
|
end
|
208
244
|
|
209
245
|
def agent_settings_file(agent_id)
|