bosh-director 1.2941.0 → 1.2949.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6ff8d3b656ced1dac2993e1b47a0bcbe7a7b6e12
4
- data.tar.gz: bc540bdd773816f632624ac090e9ea93fb2b014a
3
+ metadata.gz: f2d58f426d00ab34e503324a356f32419e8ec589
4
+ data.tar.gz: 50c33061600de723b450e53aee4234e0f84f98b3
5
5
  SHA512:
6
- metadata.gz: 404b30865cad8f8bb7bbc1e2bc355a6ed24e30194c0c7ca09f953281bd82fa6204cb0621f669de762aeac5cf27a948ad89cf0b74f3a2b81de81afe9d2c948a5b
7
- data.tar.gz: 2193105304206a1203c9c31d432e61d7acc33c56cc44bdae9b8eacd33979e96cc868e0212e3b90ace51c127ebe0b7f2cb0afae2e2a9b74aad71e79a4a0710628
6
+ metadata.gz: 3b4108244748cffa5cd372f29faddc1504f6859904bb95b6eb6ce1a86531f832e6dab33dfa2070e3df9455a3be29716512dce4b4c3b68ea2bdbaa2ded74ac8bd
7
+ data.tar.gz: aa4dddfd9fd057e4e62f5835e85487bbb4ae18ab4f926acc852cd5eb1cee6ac9f591cfd027067265ef7fef948b3e03e765a482edcf472ea897a87449c841c9c0
@@ -81,7 +81,6 @@ require 'bosh/director/job_queue'
81
81
  require 'bosh/director/lock'
82
82
  require 'bosh/director/nats_rpc'
83
83
  require 'bosh/director/network_reservation'
84
- require 'bosh/director/package_compiler'
85
84
  require 'bosh/director/problem_scanner/scanner'
86
85
  require 'bosh/director/problem_resolver'
87
86
  require 'bosh/director/resource_pool_updater'
@@ -294,7 +294,7 @@ module Bosh::Director
294
294
  deployment = @deployment_manager.find_by_name(params[:deployment_name])
295
295
 
296
296
  manifest = Psych.load(deployment.manifest)
297
- deployment_plan = DeploymentPlan::Planner.parse(manifest, {}, Config.event_log, Config.logger)
297
+ deployment_plan = DeploymentPlan::Planner.parse(manifest, deployment.cloud_config, {}, Config.event_log, Config.logger)
298
298
 
299
299
  errands = deployment_plan.jobs.select(&:can_run_as_errand?)
300
300
 
@@ -10,7 +10,7 @@ module Bosh::Director
10
10
  class << self
11
11
  include DnsHelper
12
12
 
13
- CONFIG_OPTIONS = [
13
+ attr_accessor(
14
14
  :base_dir,
15
15
  :cloud_options,
16
16
  :db,
@@ -33,17 +33,13 @@ module Bosh::Director
33
33
  :enable_snapshots,
34
34
  :max_vm_create_tries,
35
35
  :nats_uri,
36
- ]
37
-
38
- CONFIG_OPTIONS.each do |option|
39
- attr_accessor option
40
- end
36
+ )
41
37
 
42
38
  attr_reader :db_config, :redis_logger_level
43
39
 
44
40
  def clear
45
- CONFIG_OPTIONS.each do |option|
46
- self.instance_variable_set("@#{option}".to_sym, nil)
41
+ self.instance_variables.each do |ivar|
42
+ self.instance_variable_set(ivar, nil)
47
43
  end
48
44
 
49
45
  Thread.list.each do |thr|
@@ -10,11 +10,9 @@ require 'bosh/director/deployment_plan/job'
10
10
  require 'bosh/director/deployment_plan/network'
11
11
  require 'bosh/director/deployment_plan/network_subnet'
12
12
  require 'bosh/director/deployment_plan/compiled_package'
13
- require 'bosh/director/deployment_plan/preparer'
14
13
  require 'bosh/director/deployment_plan/resource_pools'
15
14
  require 'bosh/director/deployment_plan/instance_vm_binder'
16
15
  require 'bosh/director/deployment_plan/multi_job_updater'
17
- require 'bosh/director/deployment_plan/updater'
18
16
  require 'bosh/director/deployment_plan/release_version'
19
17
  require 'bosh/director/deployment_plan/resource_pool'
20
18
  require 'bosh/director/deployment_plan/stemcell'
@@ -26,3 +24,7 @@ require 'bosh/director/deployment_plan/vip_network'
26
24
  require 'bosh/director/deployment_plan/planner'
27
25
  require 'bosh/director/deployment_plan/dns_binder'
28
26
  require 'bosh/director/deployment_plan/notifier'
27
+ require 'bosh/director/deployment_plan/steps/prepare_step'
28
+ require 'bosh/director/deployment_plan/steps/update_step'
29
+ require 'bosh/director/deployment_plan/steps/package_compile_step'
30
+
@@ -23,8 +23,9 @@ module Bosh::Director
23
23
  # Binds release DB record(s) to a plan
24
24
  # @return [void]
25
25
  def bind_releases
26
- with_release_locks(@deployment_plan) do
27
- @deployment_plan.releases.each do |release|
26
+ releases = @deployment_plan.releases
27
+ with_release_locks(releases.map(&:name)) do
28
+ releases.each do |release|
28
29
  release.bind_model
29
30
  end
30
31
  end
@@ -13,13 +13,17 @@ module Bosh::Director
13
13
 
14
14
  # @param [Hash] manifest Raw deployment manifest
15
15
  # @return [DeploymentPlan::Planner] Deployment as build from deployment_spec
16
- def parse(manifest, options = {})
17
- @manifest = manifest
16
+ def parse(manifest, cloud_config, options = {})
17
+ @deployment_manifest = manifest
18
+ if cloud_config.nil?
19
+ @cloud_manifest = cloud_manifest_from_deployment_manifest @deployment_manifest
20
+ else
21
+ @cloud_manifest = cloud_config.manifest
22
+ end
18
23
 
19
- @job_states = safe_property(options, 'job_states',
20
- :class => Hash, :default => {})
24
+ @job_states = safe_property(options, 'job_states', :class => Hash, :default => {})
21
25
 
22
- @deployment = Planner.new(parse_name, options)
26
+ @deployment = Planner.new(parse_name, manifest, cloud_config, options)
23
27
 
24
28
  parse_properties
25
29
  parse_releases
@@ -35,27 +39,36 @@ module Bosh::Director
35
39
 
36
40
  private
37
41
 
42
+ CLOUD_MANIFEST_KEYS = ['resource_pools','compilation','disk_pools','networks']
43
+ def cloud_manifest_from_deployment_manifest(deployment_manifest)
44
+ cloud_manifest = {}
45
+ CLOUD_MANIFEST_KEYS.each do |key|
46
+ cloud_manifest[key] = deployment_manifest[key] if deployment_manifest.has_key? key
47
+ end
48
+ cloud_manifest
49
+ end
50
+
38
51
  def parse_name
39
- safe_property(@manifest, 'name', :class => String)
52
+ safe_property(@deployment_manifest, 'name', :class => String)
40
53
  end
41
54
 
42
55
  def parse_properties
43
- @deployment.properties = safe_property(@manifest, 'properties',
56
+ @deployment.properties = safe_property(@deployment_manifest, 'properties',
44
57
  :class => Hash, :default => {})
45
58
  end
46
59
 
47
60
  def parse_releases
48
61
  release_specs = []
49
62
 
50
- if @manifest.has_key?('release')
51
- if @manifest.has_key?('releases')
63
+ if @deployment_manifest.has_key?('release')
64
+ if @deployment_manifest.has_key?('releases')
52
65
  raise DeploymentAmbiguousReleaseSpec,
53
66
  "Deployment manifest contains both 'release' and 'releases' " +
54
67
  'sections, please use one of the two.'
55
68
  end
56
- release_specs << @manifest['release']
69
+ release_specs << @deployment_manifest['release']
57
70
  else
58
- safe_property(@manifest, 'releases', :class => Array).each do |release|
71
+ safe_property(@deployment_manifest, 'releases', :class => Array).each do |release|
59
72
  release_specs << release
60
73
  end
61
74
  end
@@ -66,7 +79,7 @@ module Bosh::Director
66
79
  end
67
80
 
68
81
  def parse_networks
69
- networks = safe_property(@manifest, 'networks', :class => Array)
82
+ networks = safe_property(@cloud_manifest, 'networks', :class => Array)
70
83
  networks.each do |network_spec|
71
84
  type = safe_property(network_spec, 'type', :class => String,
72
85
  :default => 'manual')
@@ -92,17 +105,17 @@ module Bosh::Director
92
105
  end
93
106
 
94
107
  def parse_compilation
95
- compilation_spec = safe_property(@manifest, 'compilation', :class => Hash)
108
+ compilation_spec = safe_property(@cloud_manifest, 'compilation', :class => Hash)
96
109
  @deployment.compilation = CompilationConfig.new(@deployment, compilation_spec)
97
110
  end
98
111
 
99
112
  def parse_update
100
- update_spec = safe_property(@manifest, 'update', :class => Hash)
113
+ update_spec = safe_property(@deployment_manifest, 'update', :class => Hash)
101
114
  @deployment.update = UpdateConfig.new(update_spec)
102
115
  end
103
116
 
104
117
  def parse_resource_pools
105
- resource_pools = safe_property(@manifest, 'resource_pools', :class => Array)
118
+ resource_pools = safe_property(@cloud_manifest, 'resource_pools', :class => Array)
106
119
  resource_pools.each do |rp_spec|
107
120
  @deployment.add_resource_pool(ResourcePool.new(@deployment, rp_spec, @logger))
108
121
  end
@@ -112,7 +125,7 @@ module Bosh::Director
112
125
  end
113
126
 
114
127
  def parse_disk_pools
115
- disk_pools = safe_property(@manifest, 'disk_pools', :class => Array, :optional => true)
128
+ disk_pools = safe_property(@cloud_manifest, 'disk_pools', :class => Array, :optional => true)
116
129
  return if disk_pools.nil?
117
130
  disk_pools.each do |dp_spec|
118
131
  @deployment.add_disk_pool(DiskPool.parse(dp_spec))
@@ -120,7 +133,7 @@ module Bosh::Director
120
133
  end
121
134
 
122
135
  def parse_jobs
123
- jobs = safe_property(@manifest, 'jobs', :class => Array, :default => [])
136
+ jobs = safe_property(@deployment_manifest, 'jobs', :class => Array, :default => [])
124
137
  jobs.each do |job_spec|
125
138
  state_overrides = @job_states[job_spec['name']]
126
139
  if state_overrides
@@ -6,6 +6,7 @@ module Bosh::Director
6
6
  # from the deployment manifest and the running environment.
7
7
  module DeploymentPlan
8
8
  class Planner
9
+ include LockHelper
9
10
  include DnsHelper
10
11
  include ValidationHelper
11
12
 
@@ -53,16 +54,18 @@ module Bosh::Director
53
54
  # @param [Logger]
54
55
  # logger Log for director logging
55
56
  # @return [Bosh::Director::DeploymentPlan::Planner]
56
- def self.parse(manifest, options, event_log, logger)
57
+ def self.parse(manifest, cloud_config, options, event_log, logger)
57
58
  parser = DeploymentSpecParser.new(event_log, logger)
58
- parser.parse(manifest, options)
59
+ parser.parse(manifest, cloud_config, options)
59
60
  end
60
61
 
61
- def initialize(name, options = {})
62
+ def initialize(name, manifest_text, cloud_config, options = {})
62
63
  raise ArgumentError, 'name must not be nil' unless name
63
64
  @name = name
64
- @model = nil
65
+ @manifest_text = manifest_text
66
+ @cloud_config = cloud_config
65
67
 
68
+ @model = nil
66
69
  @properties = {}
67
70
  @releases = {}
68
71
  @networks = {}
@@ -260,6 +263,29 @@ module Bosh::Director
260
263
  def rename_in_progress?
261
264
  @job_rename['old_name'] && @job_rename['new_name']
262
265
  end
266
+
267
+ def persist_updates!
268
+ #prior updates may have had release versions that we no longer use.
269
+ #remove the references to these stale releases.
270
+ stale_release_versions = (model.release_versions - releases.map(&:model))
271
+ stale_release_names = stale_release_versions.map {|version_model| version_model.release.name}
272
+ with_release_locks(stale_release_names) do
273
+ stale_release_versions.each do |release_version|
274
+ model.remove_release_version(release_version)
275
+ end
276
+ end
277
+
278
+ model.manifest = Psych.dump(@manifest_text)
279
+ model.cloud_config = @cloud_config
280
+ model.save
281
+ end
282
+
283
+ def update_stemcell_references!
284
+ current_stemcell_models = resource_pools.map { |pool| pool.stemcell.model }
285
+ model.stemcells.each do |deployment_stemcell|
286
+ deployment_stemcell.remove_deployment(model) unless current_stemcell_models.include?(deployment_stemcell)
287
+ end
288
+ end
263
289
  end
264
290
  end
265
291
  end
@@ -0,0 +1,340 @@
1
+ require 'bosh/director/compile_task_generator'
2
+
3
+ module Bosh::Director
4
+ module DeploymentPlan
5
+ module Steps
6
+ class PackageCompileStep
7
+ include LockHelper
8
+
9
+ attr_reader :compilations_performed
10
+
11
+ # @param [DeploymentPlan] deployment_plan Deployment plan
12
+ def initialize(deployment_plan)
13
+ @deployment_plan = deployment_plan
14
+
15
+ @cloud = Config.cloud
16
+ @event_log = Config.event_log
17
+ @logger = Config.logger
18
+ @director_job = Config.current_job
19
+
20
+ @tasks_mutex = Mutex.new
21
+ @network_mutex = Mutex.new
22
+ @counter_mutex = Mutex.new
23
+
24
+ compilation_config = @deployment_plan.compilation
25
+
26
+ @network = compilation_config.network
27
+ @compilation_resources = compilation_config.cloud_properties
28
+ @compilation_env = compilation_config.env
29
+
30
+ @vm_reuser = VmReuser.new
31
+
32
+ @compile_task_generator = CompileTaskGenerator.new(@logger, @event_log)
33
+
34
+ @compile_tasks = {}
35
+ @ready_tasks = []
36
+ @compilations_performed = 0
37
+ end
38
+
39
+ def perform
40
+ @logger.info('Generating a list of compile tasks')
41
+ prepare_tasks
42
+
43
+ @compile_tasks.each_value do |task|
44
+ if task.ready_to_compile?
45
+ @logger.info("Package `#{task.package.desc}' is ready to be compiled for stemcell `#{task.stemcell.desc}'")
46
+ @ready_tasks << task
47
+ end
48
+ end
49
+
50
+ if @ready_tasks.empty?
51
+ @logger.info('All packages are already compiled')
52
+ else
53
+ compile_packages
54
+ director_job_checkpoint
55
+ end
56
+ end
57
+
58
+ def compile_tasks_count
59
+ @compile_tasks.size
60
+ end
61
+
62
+ def ready_tasks_count
63
+ @tasks_mutex.synchronize { @ready_tasks.size }
64
+ end
65
+
66
+
67
+
68
+ def compile_package(task)
69
+ package = task.package
70
+ stemcell = task.stemcell
71
+
72
+ with_compile_lock(package.id, stemcell.id) do
73
+ # Check if the package was compiled in a parallel deployment
74
+ compiled_package = task.find_compiled_package(@logger, @event_log)
75
+ if compiled_package.nil?
76
+ build = Models::CompiledPackage.generate_build_number(package, stemcell)
77
+ task_result = nil
78
+
79
+ prepare_vm(stemcell) do |vm_data|
80
+ vm_metadata_updater.update(vm_data.vm, :compiling => package.name)
81
+ agent_task =
82
+ vm_data.agent.compile_package(package.blobstore_id,
83
+ package.sha1, package.name,
84
+ "#{package.version}.#{build}",
85
+ task.dependency_spec)
86
+ task_result = agent_task['result']
87
+ end
88
+
89
+ compiled_package = Models::CompiledPackage.create do |p|
90
+ p.package = package
91
+ p.stemcell = stemcell
92
+ p.sha1 = task_result['sha1']
93
+ p.build = build
94
+ p.blobstore_id = task_result['blobstore_id']
95
+ p.dependency_key = task.dependency_key
96
+ end
97
+
98
+ if Config.use_compiled_package_cache?
99
+ if BlobUtil.exists_in_global_cache?(package, task.cache_key)
100
+ @logger.info('Already exists in global package cache, skipping upload')
101
+ else
102
+ @logger.info('Uploading to global package cache')
103
+ BlobUtil.save_to_global_cache(compiled_package, task.cache_key)
104
+ end
105
+ else
106
+ @logger.info('Global blobstore not configured, skipping upload')
107
+ end
108
+
109
+ @counter_mutex.synchronize { @compilations_performed += 1 }
110
+ end
111
+
112
+ task.use_compiled_package(compiled_package)
113
+ end
114
+ end
115
+
116
+ # This method will create a VM for each stemcell in the stemcells array
117
+ # passed in. The VMs are yielded and their destruction is ensured.
118
+ # @param [Models::Stemcell] stemcell The stemcells that need to have
119
+ # compilation VMs created.
120
+ # @yield [VmData] Yields a VmData object that contains all the data for the
121
+ # VM that should be used for compilation. This may be a reused VM or a
122
+ # freshly created VM.
123
+ def prepare_vm(stemcell)
124
+ # If we're reusing VMs, try to just return an already-created VM.
125
+ if @deployment_plan.compilation.reuse_compilation_vms
126
+ vm_data = @vm_reuser.get_vm(stemcell)
127
+ if vm_data
128
+ @logger.info("Reusing compilation VM `#{vm_data.vm.cid}' for stemcell `#{stemcell.desc}'")
129
+ begin
130
+ yield vm_data
131
+ ensure
132
+ vm_data.release
133
+ end
134
+ return
135
+ end
136
+ # This shouldn't happen. If it does there's a bug.
137
+ if @vm_reuser.get_num_vms(stemcell) >=
138
+ @deployment_plan.compilation.workers
139
+ raise PackageCompilationNotEnoughWorkersForReuse,
140
+ 'There should never be more VMs for a stemcell than the number of workers in reuse_compilation_vms mode'
141
+ end
142
+ end
143
+
144
+ @logger.info("Creating compilation VM for stemcell `#{stemcell.desc}'")
145
+
146
+ reservation = reserve_network
147
+
148
+ network_settings = {
149
+ @network.name => @network.network_settings(reservation)
150
+ }
151
+
152
+ vm = VmCreator.create(@deployment_plan.model, stemcell,
153
+ @compilation_resources, network_settings,
154
+ nil, @compilation_env)
155
+ vm_data = @vm_reuser.add_vm(reservation, vm, stemcell, network_settings)
156
+
157
+ @logger.info("Configuring compilation VM: #{vm.cid}")
158
+
159
+ begin
160
+ agent = AgentClient.with_defaults(vm.agent_id)
161
+ agent.wait_until_ready
162
+
163
+ configure_vm(vm, agent, network_settings)
164
+ vm_data.agent = agent
165
+ yield vm_data
166
+ rescue RpcTimeout => e
167
+ # if we time out waiting for the agent, we should clean up the the VM
168
+ # as it will leave us in an unrecoverable state otherwise
169
+ @vm_reuser.remove_vm(vm_data)
170
+ tear_down_vm(vm_data)
171
+ raise e
172
+ ensure
173
+ vm_data.release
174
+ unless @deployment_plan.compilation.reuse_compilation_vms
175
+ tear_down_vm(vm_data)
176
+ end
177
+ end
178
+ end
179
+
180
+ private
181
+
182
+ def prepare_tasks
183
+ @event_log.begin_stage('Preparing package compilation', 1)
184
+
185
+ @event_log.track('Finding packages to compile') do
186
+ @deployment_plan.jobs.each do |job|
187
+ stemcell = job.resource_pool.stemcell
188
+
189
+ template_descs = job.templates.map do |t|
190
+ # we purposefully did NOT inline those because
191
+ # when instance_double blows up,
192
+ # it's obscure which double is at fault
193
+ release_name = t.release.name
194
+ template_name = t.name
195
+ "`#{release_name}/#{template_name}'"
196
+ end
197
+ @logger.info("Job templates #{template_descs.join(', ')} need to run on stemcell `#{stemcell.model.desc}'")
198
+
199
+ job.templates.each do |template|
200
+ template.package_models.each do |package|
201
+ @compile_task_generator.generate!(@compile_tasks, job, template, package, stemcell.model)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ def tear_down_vm(vm_data)
209
+ vm = vm_data.vm
210
+ if vm.exists?
211
+ reservation = vm_data.reservation
212
+ @logger.info("Deleting compilation VM: #{vm.cid}")
213
+ @cloud.delete_vm(vm.cid)
214
+ vm.destroy
215
+ release_network(reservation)
216
+ end
217
+ end
218
+
219
+ def reserve_network
220
+ reservation = NetworkReservation.new_dynamic
221
+
222
+ @network_mutex.synchronize do
223
+ @network.reserve(reservation)
224
+ end
225
+
226
+ unless reservation.reserved?
227
+ raise PackageCompilationNetworkNotReserved,
228
+ "Could not reserve network for package compilation: #{reservation.error}"
229
+ end
230
+
231
+ reservation
232
+ end
233
+
234
+ def release_network(reservation)
235
+ @network_mutex.synchronize do
236
+ @network.release(reservation)
237
+ end
238
+ end
239
+
240
+ def compile_packages
241
+ @event_log.begin_stage('Compiling packages', compilation_count)
242
+ number_of_workers = @deployment_plan.compilation.workers
243
+
244
+ begin
245
+ ThreadPool.new(:max_threads => number_of_workers).wrap do |pool|
246
+ loop do
247
+ # process as many tasks without waiting
248
+ loop do
249
+ break if director_job_cancelled?
250
+ task = @tasks_mutex.synchronize { @ready_tasks.pop }
251
+ break if task.nil?
252
+
253
+ pool.process { process_task(task) }
254
+ end
255
+
256
+ break if !pool.working? && (director_job_cancelled? || @ready_tasks.empty?)
257
+ sleep(0.1)
258
+ end
259
+ end
260
+ ensure
261
+ # Delete all of the VMs if we were reusing compilation VMs. This can't
262
+ # happen until everything was done compiling.
263
+ if @deployment_plan.compilation.reuse_compilation_vms
264
+ # Using a new ThreadPool instead of reusing the previous one,
265
+ # as if there's a failed compilation, the thread pool will stop
266
+ # processing any new thread.
267
+ ThreadPool.new(:max_threads => number_of_workers).wrap do |pool|
268
+ @vm_reuser.each do |vm_data|
269
+ pool.process { tear_down_vm(vm_data) }
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ def enqueue_unblocked_tasks(task)
277
+ @tasks_mutex.synchronize do
278
+ @logger.info("Unblocking dependents of `#{task.package.desc}` for `#{task.stemcell.desc}`")
279
+ task.dependent_tasks.each do |dep_task|
280
+ if dep_task.ready_to_compile?
281
+ @logger.info("Package `#{dep_task.package.desc}' now ready to be compiled for `#{dep_task.stemcell.desc}'")
282
+ @ready_tasks << dep_task
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ def process_task(task)
289
+ package_desc = task.package.desc
290
+ stemcell_desc = task.stemcell.desc
291
+ task_desc = "package `#{package_desc}' for stemcell `#{stemcell_desc}'"
292
+
293
+ with_thread_name("compile_package(#{package_desc}, #{stemcell_desc})") do
294
+ if director_job_cancelled?
295
+ @logger.info("Cancelled compiling #{task_desc}")
296
+ else
297
+ @event_log.track(package_desc) do
298
+ @logger.info("Compiling #{task_desc}")
299
+ compile_package(task)
300
+ @logger.info("Finished compiling #{task_desc}")
301
+ enqueue_unblocked_tasks(task)
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ def director_job_cancelled?
308
+ @director_job && @director_job.task_cancelled?
309
+ end
310
+
311
+ def director_job_checkpoint
312
+ @director_job.task_checkpoint if @director_job
313
+ end
314
+
315
+ def configure_vm(vm, agent, network_settings)
316
+ state = {
317
+ 'deployment' => @deployment_plan.name,
318
+ 'resource_pool' => 'package_compiler',
319
+ 'networks' => network_settings
320
+ }
321
+
322
+ vm.update(:apply_spec => state)
323
+ agent.apply(state)
324
+ end
325
+
326
+ def compilation_count
327
+ counter = 0
328
+ @compile_tasks.each_value do |task|
329
+ counter += 1 unless task.compiled?
330
+ end
331
+ counter
332
+ end
333
+
334
+ def vm_metadata_updater
335
+ @vm_metadata_updater ||= VmMetadataUpdater.build
336
+ end
337
+ end
338
+ end
339
+ end
340
+ end