Class: Bosh::Director::DeploymentPlan::Instance

Inherits:
Object
  • Object
show all
Includes:
Bosh::Director::DnsHelper
Defined in:
lib/bosh/director/deployment_plan/instance.rb

Overview

Represents a single job instance.

Constant Summary

Constants included from Bosh::Director::DnsHelper

Bosh::Director::DnsHelper::SOA, Bosh::Director::DnsHelper::TTL_4H, Bosh::Director::DnsHelper::TTL_5M

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Bosh::Director::DnsHelper

#add_default_dns_server, #canonical, #default_dns_server, #delete_dns_records, #delete_empty_domain, #dns_domain_name, #dns_ns_record, #dns_servers, #flush_dns_cache, #invalid_dns, #reverse_domain, #reverse_host, #update_dns_a_record, #update_dns_ptr_record

Constructor Details

#initialize(job, index, logger) ⇒ Instance

Creates a new instance specification based on the job and index.

Parameters:



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/bosh/director/deployment_plan/instance.rb', line 46

def initialize(job, index, logger)
  @job = job
  @index = index
  @logger = logger

  @configuration_hash = nil
  @template_hashes = nil
  @vm = nil
  @current_state = nil

  @network_reservations = {}
  @state = job.instance_state(@index)

  # Expanding virtual states
  case @state
    when 'recreate'
      @recreate = true
      @state = 'started'
    when 'restart'
      @restart = true
      @state = 'started'
  end
end

Instance Attribute Details

#configuration_hashString

Returns Checksum all of the configuration templates.

Returns:

  • (String)

    Checksum all of the configuration templates



17
18
19
# File 'lib/bosh/director/deployment_plan/instance.rb', line 17

def configuration_hash
  @configuration_hash
end

#current_stateHash (readonly)

Returns current state as provided by the BOSH Agent.

Returns:

  • (Hash)

    current state as provided by the BOSH Agent



32
33
34
# File 'lib/bosh/director/deployment_plan/instance.rb', line 32

def current_state
  @current_state
end

#indexInteger (readonly)

Returns Instance index.

Returns:

  • (Integer)

    Instance index



11
12
13
# File 'lib/bosh/director/deployment_plan/instance.rb', line 11

def index
  @index
end

#jobDeploymentPlan::Job (readonly)

Returns Associated job.

Returns:



8
9
10
# File 'lib/bosh/director/deployment_plan/instance.rb', line 8

def job
  @job
end

#modelModels::Instance (readonly)

Returns Instance model.

Returns:



14
15
16
# File 'lib/bosh/director/deployment_plan/instance.rb', line 14

def model
  @model
end

#network_reservationsHash<String, NetworkReservation>

Returns network reservations.

Returns:



26
27
28
# File 'lib/bosh/director/deployment_plan/instance.rb', line 26

def network_reservations
  @network_reservations
end

#recreateBoolean

Returns true if this instance needs to be recreated.

Returns:

  • (Boolean)

    true if this instance needs to be recreated



38
39
40
# File 'lib/bosh/director/deployment_plan/instance.rb', line 38

def recreate
  @recreate
end

#rendered_templates_archiveBosh::Director::Core::Templates::RenderedTemplatesArchive

Returns:

  • (Bosh::Director::Core::Templates::RenderedTemplatesArchive)


23
24
25
# File 'lib/bosh/director/deployment_plan/instance.rb', line 23

def rendered_templates_archive
  @rendered_templates_archive
end

#restartBoolean

Returns true if this instance needs to be restarted.

Returns:

  • (Boolean)

    true if this instance needs to be restarted



41
42
43
# File 'lib/bosh/director/deployment_plan/instance.rb', line 41

def restart
  @restart
end

#stateString

Returns job state.

Returns:

  • (String)

    job state



29
30
31
# File 'lib/bosh/director/deployment_plan/instance.rb', line 29

def state
  @state
end

#template_hashesHash

Returns A hash of template SHA1 hashes.

Returns:

  • (Hash)

    A hash of template SHA1 hashes



20
21
22
# File 'lib/bosh/director/deployment_plan/instance.rb', line 20

def template_hashes
  @template_hashes
end

#vmDeploymentPlan::Vm (readonly)

Returns Associated resource pool VM.

Returns:



35
36
37
# File 'lib/bosh/director/deployment_plan/instance.rb', line 35

def vm
  @vm
end

Instance Method Details

#add_network_reservation(name, reservation) ⇒ Object

Adds a new network to this instance

Parameters:



173
174
175
176
177
178
179
180
181
182
# File 'lib/bosh/director/deployment_plan/instance.rb', line 173

def add_network_reservation(name, reservation)
  old_reservation = @network_reservations[name]

  if old_reservation
    raise NetworkReservationAlreadyExists,
          "`#{self}' already has reservation " +
          "for network `#{name}', IP #{old_reservation.ip}"
  end
  @network_reservations[name] = reservation
end

#allocate_vmvoid

This method returns an undefined value.

Allocates an VM in this job resource pool and binds current instance to that VM.



473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/bosh/director/deployment_plan/instance.rb', line 473

def allocate_vm
  resource_pool = @job.resource_pool
  vm = resource_pool.allocate_vm
  network = resource_pool.network

  if vm.model
    # There's already a resource pool VM that can become our instance,
    # so we can try to reuse its reservation
    instance_reservation = @network_reservations[network.name]
    if instance_reservation
      instance_reservation.take(vm.network_reservation)
    end
  else
    # VM is not created yet: let's just make it reference this instance
    # so later it knows what it needs to become
    vm.bound_instance = self

    # this also means we no longer need previous VM network reservation
    # (instance has its own)
    vm.release_reservation
  end

  @vm = vm
end

#apply_partial_vm_stateObject



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/bosh/director/deployment_plan/instance.rb', line 97

def apply_partial_vm_state
  @logger.info('Applying partial VM state')

  state = @vm.current_state
  state['job'] = job.spec
  state['index'] = index

  # Apply the assignment to the VM
  agent = AgentClient.with_defaults(@vm.model.agent_id)
  agent.apply(state)

  # Our assumption here is that director database access
  # is much less likely to fail than VM agent communication
  # so we only update database after we see a successful agent apply.
  # If database update fails subsequent deploy will try to
  # assign a new VM to this instance which is ok.
  @vm.model.db.transaction do
    @vm.model.update(:apply_spec => state)
    @model.update(:vm => @vm.model)
  end

  @current_state = state
end

#apply_vm_stateObject



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/bosh/director/deployment_plan/instance.rb', line 121

def apply_vm_state
  @logger.info('Applying VM state')

  state = {
    'deployment' => @job.deployment.name,
    'networks' => network_settings,
    'resource_pool' => @job.resource_pool.spec,
    'job' => @job.spec,
    'index' => @index,
  }

  if disk_size > 0
    state['persistent_disk'] = disk_size
  end

  @model.vm.update(:apply_spec => state)

  agent = AgentClient.with_defaults(@model.vm.agent_id)
  agent.apply(state)

  # Agent will potentially return modified version of state
  # with resolved dynamic networks information
  @current_state = agent.get_state
end

#bind_existing_instance(instance_model, state, reservations) ⇒ Object

Updates this domain object to reflect an existing instance running on an existing vm



87
88
89
90
91
92
93
94
95
# File 'lib/bosh/director/deployment_plan/instance.rb', line 87

def bind_existing_instance(instance_model, state, reservations)
  check_model_not_bound

  @model = instance_model
  @current_state = state

  take_network_reservations(reservations)
  add_allocated_vm(instance_model.vm, state)
end

#bind_to_vm_model(vm_model) ⇒ Object



447
448
449
450
451
# File 'lib/bosh/director/deployment_plan/instance.rb', line 447

def bind_to_vm_model(vm_model)
  @model.update(vm: vm_model)
  @vm.model = vm_model
  @vm.bound_instance = self
end

#bind_unallocated_vmvoid

This method returns an undefined value.

Looks up instance model in DB and binds it to this instance spec. Instance model is created if it’s not found in DB. New VM is allocated if instance DB record doesn’t reference one.



78
79
80
81
82
83
# File 'lib/bosh/director/deployment_plan/instance.rb', line 78

def bind_unallocated_vm
  @model ||= find_or_create_model
  if @model.vm.nil?
    allocate_vm
  end
end

#changed?Boolean

Returns true if the any of the expected specifications differ from the ones provided by the VM

Returns:

  • (Boolean)

    returns true if the any of the expected specifications differ from the ones provided by the VM



384
385
386
# File 'lib/bosh/director/deployment_plan/instance.rb', line 384

def changed?
  !changes.empty?
end

#changesSet<Symbol>

Returns a set of all of the specification differences

Returns:

  • (Set<Symbol>)

    returns a set of all of the specification differences



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/bosh/director/deployment_plan/instance.rb', line 390

def changes
  changes = Set.new
  unless @state == 'detached' && @current_state.nil?
    changes << :restart if @restart
    changes << :resource_pool if resource_pool_changed?
    changes << :network if networks_changed?
    changes << :packages if packages_changed?
    changes << :persistent_disk if persistent_disk_changed?
    changes << :configuration if configuration_changed?
    changes << :job if job_changed?
    changes << :state if state_changed?
    changes << :dns if dns_changed?
    changes << :trusted_certs if trusted_certs_changed?
  end
  changes
end

#configuration_changed?Boolean

Returns true if the expected configuration hash differs from the one provided by the VM

Returns:

  • (Boolean)

    returns true if the expected configuration hash differs from the one provided by the VM



305
306
307
# File 'lib/bosh/director/deployment_plan/instance.rb', line 305

def configuration_changed?
  configuration_hash != @current_state['configuration_hash']
end

#disk_cloud_propertiesHash

Returns persistent disk cloud properties.

Returns:

  • (Hash)

    persistent disk cloud properties



238
239
240
241
242
243
244
245
246
# File 'lib/bosh/director/deployment_plan/instance.rb', line 238

def disk_cloud_properties
  check_model_bound

  if @model.persistent_disk
    @model.persistent_disk.cloud_properties
  else
    {}
  end
end

#disk_currently_attached?Boolean

Returns true if the persistent disk is attached to the VM

Returns:

  • (Boolean)

    returns true if the persistent disk is attached to the VM



267
268
269
# File 'lib/bosh/director/deployment_plan/instance.rb', line 267

def disk_currently_attached?
  current_state['persistent_disk'].to_i > 0
end

#disk_sizeInteger

Returns persistent disk size.

Returns:

  • (Integer)

    persistent disk size



226
227
228
229
230
231
232
233
234
# File 'lib/bosh/director/deployment_plan/instance.rb', line 226

def disk_size
  check_model_bound

  if @model.persistent_disk
    @model.persistent_disk.size
  else
    0
  end
end

#dns_changed?Boolean

Returns true if the DNS records configured for the instance differ from the ones configured on the DNS server

Returns:

  • (Boolean)

    returns true if the DNS records configured for the instance differ from the ones configured on the DNS server



346
347
348
349
350
351
352
353
354
355
# File 'lib/bosh/director/deployment_plan/instance.rb', line 346

def dns_changed?
  if Config.dns_enabled?
    dns_record_info.any? do |name, ip|
      Models::Dns::Record.find(:name => name, :type => 'A',
                               :content => ip).nil?
    end
  else
    false
  end
end

#dns_record_infoHash<String, String>

Returns dns record hash of dns name and IP.

Returns:

  • (Hash<String, String>)

    dns record hash of dns name and IP



250
251
252
253
254
255
256
257
# File 'lib/bosh/director/deployment_plan/instance.rb', line 250

def dns_record_info
  dns_record_info = {}
  network_settings.each do |network_name, network|
    name = dns_record_name(network_name)
    dns_record_info[name] = network['ip']
  end
  dns_record_info
end

#dns_record_name(network_name) ⇒ String

Returns dns record name.

Returns:

  • (String)

    dns record name



261
262
263
# File 'lib/bosh/director/deployment_plan/instance.rb', line 261

def dns_record_name(network_name)
  [index, job.canonical_name, canonical(network_name), job.deployment.canonical_name, dns_domain_name].join('.')
end

#find_or_create_modelModels::Instance

Looks up instance model in DB

Returns:



455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
# File 'lib/bosh/director/deployment_plan/instance.rb', line 455

def find_or_create_model
  if @job.deployment.model.nil?
    raise DirectorError, 'Deployment model is not bound'
  end

  conditions = {
    deployment_id: @job.deployment.model.id,
    job: @job.name,
    index: @index
  }

  Models::Instance.find_or_create(conditions) do |model|
    model.state = 'started'
  end
end

#job_changed?Boolean

Returns true if the expected job configuration differs from the one provided by the VM

Returns:

  • (Boolean)

    returns true if the expected job configuration differs from the one provided by the VM



312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/bosh/director/deployment_plan/instance.rb', line 312

def job_changed?
  return true if @current_state.nil?

  job_spec = @job.spec
  if job_spec != @current_state['job']
    # The agent job spec could be in legacy form.  job_spec cannot be,
    # though, because we got it from the spec function in job.rb which
    # automatically makes it non-legacy.
    return job_spec != Job.convert_from_legacy_spec(@current_state['job'])
  end
  return false
end

#network_settingsHash

Returns BOSH network settings used for Agent apply call.

Returns:

  • (Hash)

    BOSH network settings used for Agent apply call



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/bosh/director/deployment_plan/instance.rb', line 186

def network_settings
  default_properties = {}
  @job.default_network.each do |key, value|
    (default_properties[value] ||= []) << key
  end

  network_settings = {}
  @network_reservations.each do |name, reservation|
    network = @job.deployment.network(name)
    network_settings[name] = network.network_settings(reservation, default_properties[name])

    # Temporary hack for running errands.
    # We need to avoid RunErrand task thinking that
    # network configuration for errand VM differs
    # from network configuration for its Instance.
    #
    # Obviously this does not account for other changes
    # in network configuration that errand job might need.
    # (e.g. errand job desires static ip)
    if @job.starts_on_deploy?
      network_settings[name]['dns_record_name'] = dns_record_name(name)
    end

    # Somewhat of a hack: for dynamic networks we might know IP address, Netmask & Gateway
    # if they're featured in agent state, in that case we put them into network spec to satisfy
    # ConfigurationHasher in both agent and director.
    if @current_state.is_a?(Hash) &&
        @current_state['networks'].is_a?(Hash) &&
        @current_state['networks'][name].is_a?(Hash) &&
        network_settings[name]['type'] == 'dynamic'
      %w(ip netmask gateway).each do |key|
        network_settings[name][key] = @current_state['networks'][name][key]
      end
    end
  end
  network_settings
end

#networks_changed?Boolean

Returns true if the network configuration changed

Returns:

  • (Boolean)

    returns true if the network configuration changed



273
274
275
# File 'lib/bosh/director/deployment_plan/instance.rb', line 273

def networks_changed?
  network_settings != @current_state['networks']
end

#packages_changed?Boolean

Returns true if the expected packaged of the running instance differ from the ones provided by the VM

Returns:

  • (Boolean)

    returns true if the expected packaged of the running instance differ from the ones provided by the VM



328
329
330
# File 'lib/bosh/director/deployment_plan/instance.rb', line 328

def packages_changed?
  @job.package_spec != @current_state['packages']
end

#persistent_disk_changed?Boolean

Returns true if the expected persistent disk or cloud_properties differs from the state currently configured on the VM

Returns:

  • (Boolean)

    returns true if the expected persistent disk or cloud_properties differs from the state currently configured on the VM



335
336
337
338
339
340
341
# File 'lib/bosh/director/deployment_plan/instance.rb', line 335

def persistent_disk_changed?
  new_disk_size = @job.persistent_disk_pool ? @job.persistent_disk_pool.disk_size : 0
  new_disk_cloud_properties = @job.persistent_disk_pool ? @job.persistent_disk_pool.cloud_properties : {}
  return true if new_disk_size != disk_size

  new_disk_size != 0 && new_disk_cloud_properties != disk_cloud_properties
end

#resource_pool_changed?Boolean

Returns true if the expected resource pool differs from the one provided by the VM

Returns:

  • (Boolean)

    returns true if the expected resource pool differs from the one provided by the VM



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/bosh/director/deployment_plan/instance.rb', line 279

def resource_pool_changed?
  if @recreate || @job.deployment.recreate
    return true
  end

  if @job.resource_pool.spec != @current_state['resource_pool']
    return true
  end

  # env is not a part of a resource pool spec but rather gets persisted
  # in director DB, hence the check below
  # NOTE: we only update VMs that have env persisted to avoid recreating
  # everything, so if the director gets updated from the version that
  # doesn't persist VM env to the version that does, there needs to
  # be at least one deployment that recreates all VMs before the following
  # code path gets exercised.
  if @model && @model.vm && @model.vm.env && @job.resource_pool.env != @model.vm.env
    return true
  end

  false
end

#specHash<String, Object>

Instance spec that’s passed to the VM during the BOSH Agent apply call. It’s what’s used for comparing the expected vs the actual state.

Returns:



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/bosh/director/deployment_plan/instance.rb', line 411

def spec
  spec = {
    'deployment' => @job.deployment.name,
    'job' => job.spec,
    'index' => index,
    'networks' => network_settings,
    'resource_pool' => job.resource_pool.spec,
    'packages' => job.package_spec,
    'configuration_hash' => configuration_hash,
    'properties' => job.properties,
    'dns_domain_name' => dns_domain_name
  }

  if job.persistent_disk_pool
    # supply both for reverse compatibility with old agent
    spec['persistent_disk'] = job.persistent_disk_pool.disk_size
    # old agents will ignore this pool
    spec['persistent_disk_pool'] = job.persistent_disk_pool.spec
  else
    spec['persistent_disk'] = 0
  end

  if template_hashes
    spec['template_hashes'] = template_hashes
  end

  # Ruby BOSH Agent does not look at 'rendered_templates_archive'
  # since it renders job templates and then compares template hashes.
  # Go BOSH Agent has no ability to render ERB so pre-rendered templates are provided.
  if rendered_templates_archive
    spec['rendered_templates_archive'] = rendered_templates_archive.spec
  end

  spec
end

#state_changed?Boolean

Checks if agent view of the instance state is consistent with target instance state.

In case the instance current state is ‘detached’ we should never get to this method call.

Returns:

  • (Boolean)

    returns true if the expected job state differs from the one provided by the VM



365
366
367
368
369
# File 'lib/bosh/director/deployment_plan/instance.rb', line 365

def state_changed?
  @state == 'detached' ||
    @state == 'started' && @current_state['job_state'] != 'running' ||
    @state == 'stopped' && @current_state['job_state'] == 'running'
end

#sync_state_with_dbvoid

This method returns an undefined value.

Syncs instance state with instance model in DB. This is needed because not all instance states are available in the deployment manifest and we we cannot really persist this data in the agent state (as VM might be stopped or detached).



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/bosh/director/deployment_plan/instance.rb', line 152

def sync_state_with_db
  check_model_bound

  if @state
    # Deployment plan explicitly sets state for this instance
    @model.update(:state => @state)
  elsif @model.state
    # Instance has its state persisted from the previous deployment
    @state = @model.state
  else
    # Target instance state should either be persisted in DB or provided
    # via deployment plan, otherwise something is really wrong
    raise InstanceTargetStateUndefined,
          "Instance `#{self}' target state cannot be determined"
  end
end

#to_sObject



70
71
72
# File 'lib/bosh/director/deployment_plan/instance.rb', line 70

def to_s
  "#{@job.name}/#{@index}"
end

#trusted_certs_changed?Boolean

Checks if the target VM already has the same set of trusted SSL certificates as the director currently wants to install on all managed VMs. This will differ for VMs that existed before the director’s configuration changed.

Returns:

  • (Boolean)

    true if the VM needs to be sent a new set of trusted certificates



377
378
379
# File 'lib/bosh/director/deployment_plan/instance.rb', line 377

def trusted_certs_changed?
  Digest::SHA1.hexdigest(Bosh::Director::Config.trusted_certs) != @model.vm.trusted_certs_sha1
end