Class: Bosh::Director::DeploymentPlan::Job

Inherits:
Object
  • Object
show all
Includes:
Common::PropertyHelper, Bosh::Director::DnsHelper, IpUtil, ValidationHelper
Defined in:
lib/bosh/director/deployment_plan/job.rb

Constant Summary collapse

VALID_JOB_STATES =

started, stopped and detached are real states (persisting in DB and reflecting target instance state) recreate and restart are two virtual states (both set target instance state to “started” and set appropriate instance spec modifiers)

%w(started stopped detached recreate restart)

Constants included from Bosh::Director::DnsHelper

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ValidationHelper

#invalid_type, #safe_property

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, #invalid_dns, #reverse_domain, #reverse_host, #update_dns_a_record, #update_dns_ptr_record

Methods included from IpUtil

#each_ip, #format_ip, #ip_to_i, #ip_to_netaddr, #process_range

Constructor Details

#initialize(deployment, job_spec) ⇒ Job



79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/bosh/director/deployment_plan/job.rb', line 79

def initialize(deployment, job_spec)
  @deployment = deployment
  @job_spec = job_spec

  @release = nil
  @templates = []
  @all_properties = nil # All properties available to job
  @properties = nil # Actual job properties

  @error_mutex = Mutex.new
  @packages = {}
  @halt = false
  @unneeded_instances = []
end

Instance Attribute Details

#canonical_nameString



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

def canonical_name
  @canonical_name
end

#default_networkDeploymentPlan::Network Job default network



39
40
41
# File 'lib/bosh/director/deployment_plan/job.rb', line 39

def default_network
  @default_network
end

#deploymentDeploymentPlan



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

def deployment
  @deployment
end

#halt_exceptionException



66
67
68
# File 'lib/bosh/director/deployment_plan/job.rb', line 66

def halt_exception
  @halt_exception
end

#instance_statesHash<Integer, String>



62
63
64
# File 'lib/bosh/director/deployment_plan/job.rb', line 62

def instance_states
  @instance_states
end

#nameString



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

def name
  @name
end

#packagesHash<String, DeploymentPlan::Package] Packages included into this job



49
50
51
# File 'lib/bosh/director/deployment_plan/job.rb', line 49

def packages
  @packages
end

#persistent_diskInteger



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

def persistent_disk
  @persistent_disk
end

#propertiesHash



45
46
47
# File 'lib/bosh/director/deployment_plan/job.rb', line 45

def properties
  @properties
end

#releaseDeploymentPlan::ReleaseVersion



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

def release
  @release
end

#resource_poolDeploymentPlan::ResourcePool



36
37
38
# File 'lib/bosh/director/deployment_plan/job.rb', line 36

def resource_pool
  @resource_pool
end

#stateString



59
60
61
# File 'lib/bosh/director/deployment_plan/job.rb', line 59

def state
  @state
end

#templatesArray<DeploymentPlan::Template] Templates included into the job



42
43
44
# File 'lib/bosh/director/deployment_plan/job.rb', line 42

def templates
  @templates
end

#unneeded_instancesArray<Models::Instance>



56
57
58
# File 'lib/bosh/director/deployment_plan/job.rb', line 56

def unneeded_instances
  @unneeded_instances
end

#updateDeploymentPlan::UpdateConfig



52
53
54
# File 'lib/bosh/director/deployment_plan/job.rb', line 52

def update
  @update
end

Class Method Details

.convert_from_legacy_spec(job_spec) ⇒ Object

Takes in a job spec and returns a job spec in the new format, if it needs to be modified. The new format has “templates” key, which is an array with each template’s data. This is used for job collocation, specifically for the agent’s current job spec when compared to the director’s. We only convert their template to a single array entry because it should be impossible for the agent to have a job spec with multiple templates in legacy form.



117
118
119
120
121
122
123
124
125
126
# File 'lib/bosh/director/deployment_plan/job.rb', line 117

def self.convert_from_legacy_spec(job_spec)
  return job_spec if !self.is_legacy_spec?(job_spec)
  template = {
    "name" => job_spec["template"],
    "version" => job_spec["version"],
    "sha1" => job_spec["sha1"],
    "blobstore_id" => job_spec["blobstore_id"]
  }
  job_spec["templates"] = [template]
end

.is_legacy_spec?(job_spec) ⇒ Boolean



106
107
108
# File 'lib/bosh/director/deployment_plan/job.rb', line 106

def self.is_legacy_spec?(job_spec)
  !job_spec.has_key?("templates")
end

.parse(deployment, job_spec) ⇒ Bosh::Director::DeploymentPlan::Job



71
72
73
74
75
# File 'lib/bosh/director/deployment_plan/job.rb', line 71

def self.parse(deployment, job_spec)
  job = new(deployment, job_spec)
  job.parse
  job
end

Instance Method Details

#bind_propertiesObject

Extracts only the properties needed by this job. This is decoupled from parsing properties because templates need to be bound to their models before ‘bind_properties’ is being called (as we persist job template property definitions in DB).



428
429
430
# File 'lib/bosh/director/deployment_plan/job.rb', line 428

def bind_properties
  @properties = filter_properties(@all_properties)
end

#instance(index) ⇒ DeploymentPlan::Instance

Returns job instance by index



185
186
187
# File 'lib/bosh/director/deployment_plan/job.rb', line 185

def instance(index)
  @instances[index]
end

#instance_state(index) ⇒ String?

Returns the state state of job instance by its index



192
193
194
# File 'lib/bosh/director/deployment_plan/job.rb', line 192

def instance_state(index)
  @instance_states[index] || @state
end

#instancesArray<DeploymentPlan::Instance>

Returns all instances of this job



178
179
180
# File 'lib/bosh/director/deployment_plan/job.rb', line 178

def instances
  @instances
end

#package_specHash<String, Hash>

Returns package specs for all packages in the job indexed by package name. To be used by all instances of the job to populate agent state.



167
168
169
170
171
172
173
174
# File 'lib/bosh/director/deployment_plan/job.rb', line 167

def package_spec
  result = {}
  @packages.each do |name, package|
    result[name] = package.spec
  end

  result.select { |name, _| run_time_dependencies.include? name }
end

#parseObject



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/bosh/director/deployment_plan/job.rb', line 94

def parse
  parse_name
  parse_release
  parse_template
  parse_disk
  parse_properties
  parse_resource_pool
  parse_update_config
  parse_instances
  parse_networks
end

#parse_diskObject



262
263
264
# File 'lib/bosh/director/deployment_plan/job.rb', line 262

def parse_disk
  @persistent_disk = safe_property(@job_spec, "persistent_disk", :class => Integer, :default => 0)
end

#parse_instancesObject



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/bosh/director/deployment_plan/job.rb', line 304

def parse_instances
  @instances = []
  @instance_states = {}

  @state = safe_property(@job_spec, "state", class: String, optional: true)
  job_size = safe_property(@job_spec, "instances", class: Integer)
  instance_states = safe_property(@job_spec, "instance_states", class: Hash, default: {})

  instance_states.each_pair do |index, state|
    begin
      index = Integer(index)
    rescue ArgumentError
      raise JobInvalidInstanceIndex,
        "Invalid job index `#{index}', integer expected"
    end

    unless (0...job_size).include?(index)
      raise JobInvalidInstanceIndex,
        "`#{@name}/#{index}' is outside of (0..#{job_size-1}) range"
    end

    unless VALID_JOB_STATES.include?(state)
      raise JobInvalidInstanceState,
        "Invalid state `#{state}' for `#{@name}/#{index}', valid states are: #{VALID_JOB_STATES.join(", ")}"
    end

    @instance_states[index] = state
  end

  if @state && !VALID_JOB_STATES.include?(@state)
    raise JobInvalidJobState,
      "Invalid state `#{@state}' for `#{@name}', valid states are: #{VALID_JOB_STATES.join(", ")}"
  end

  job_size.times do |index|
    @instances[index] = Instance.new(self, index)
    @resource_pool.reserve_capacity(1)
  end
end

#parse_nameObject



215
216
217
218
# File 'lib/bosh/director/deployment_plan/job.rb', line 215

def parse_name
  @name = safe_property(@job_spec, "name", :class => String)
  @canonical_name = canonical(@name)
end

#parse_networksObject



344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/bosh/director/deployment_plan/job.rb', line 344

def parse_networks
  @default_network = {}

  network_specs = safe_property(@job_spec, "networks", :class => Array)
  if network_specs.empty?
    raise JobMissingNetwork,
          "Job `#{@name}' must specify at least one network"
  end

  network_specs.each do |network_spec|
    network_name = safe_property(network_spec, "name", :class => String)
    network = @deployment.network(network_name)
    if network.nil?
      raise JobUnknownNetwork,
            "Job `#{@name}' references an unknown network `#{network_name}'"
    end

    static_ips = nil
    if network_spec["static_ips"]
      static_ips = []
      each_ip(network_spec["static_ips"]) do |ip|
        static_ips << ip
      end
      if static_ips.size != @instances.size
        raise JobNetworkInstanceIpMismatch,
              "Job `#{@name}' has #{@instances.size} instances but was allocated #{static_ips.size} static IPs"
      end
    end

    default_network = safe_property(network_spec, "default",
                                    :class => Array, :optional => true)
    if default_network
      default_network.each do |property|
        unless Network::VALID_DEFAULTS.include?(property)
          raise JobNetworkInvalidDefault,
                "Job `#{@name}' specified an invalid default network property `#{property}', " +
                "valid properties are: " + Network::VALID_DEFAULTS.join(", ")
        end

        if @default_network[property]
          raise JobNetworkMultipleDefaults,
                "Job `#{@name}' specified more than one network to contain default #{property}"
        else
          @default_network[property] = network_name
        end
      end
    end

    @instances.each_with_index do |instance, index|
      reservation = NetworkReservation.new
      if static_ips
        reservation.ip = static_ips[index]
        reservation.type = NetworkReservation::STATIC
      else
        reservation.type = NetworkReservation::DYNAMIC
      end
      instance.add_network_reservation(network_name, reservation)
    end
  end

  if network_specs.size > 1
    missing_default_properties = Network::VALID_DEFAULTS.dup
    @default_network.each_key do |key|
      missing_default_properties.delete(key)
    end
    unless missing_default_properties.empty?
      raise JobNetworkMissingDefault,
            "Job `#{@name}' must specify which network is default for " +
            missing_default_properties.sort.join(", ") + ", since it has more than one network configured"
    end
  else
    # Set the default network to the one and only available network
    # (if not specified already)
    network = safe_property(network_specs[0], "name", :class => String)
    Network::VALID_DEFAULTS.each do |property|
      @default_network[property] ||= network
    end
  end
end

#parse_propertiesObject



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/bosh/director/deployment_plan/job.rb', line 266

def parse_properties
  # Manifest can contain global and per-job properties section
  job_properties = safe_property(@job_spec, "properties", :class => Hash, :optional => true)

  @all_properties = Bosh::Common::DeepCopy.copy(deployment.properties)

  if job_properties
    @all_properties.recursive_merge!(job_properties)
  end

  mappings = safe_property(@job_spec, "property_mappings", :class => Hash, :default => {})

  mappings.each_pair do |to, from|
    resolved = lookup_property(@all_properties, from)

    if resolved.nil?
      raise JobInvalidPropertyMapping,
            "Cannot satisfy property mapping `#{to}: #{from}', as `#{from}' is not in deployment properties"
    end

    @all_properties[to] = resolved
  end
end

#parse_releaseObject



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/bosh/director/deployment_plan/job.rb', line 220

def parse_release
  release_name = safe_property(@job_spec, "release", :class => String,
                               :optional => true)

  if release_name.nil?
    if @deployment.releases.size == 1
      @release = @deployment.releases.first
    else
      raise JobMissingRelease,
            "Cannot tell what release job `#{@name}' supposed to use, please reference an existing release"
    end
  else
    @release = @deployment.release(release_name)
  end

  if @release.nil?
    raise JobUnknownRelease,
          "Job `#{@name}' references an unknown release `#{release_name}'"
  end
end

#parse_resource_poolObject



290
291
292
293
294
295
296
297
# File 'lib/bosh/director/deployment_plan/job.rb', line 290

def parse_resource_pool
  resource_pool_name = safe_property(@job_spec, "resource_pool", class: String)
  @resource_pool = deployment.resource_pool(resource_pool_name)
  if @resource_pool.nil?
    raise JobUnknownResourcePool,
          "Job `#{@name}' references an unknown resource pool `#{resource_pool_name}'"
  end
end

#parse_templateObject



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/bosh/director/deployment_plan/job.rb', line 241

def parse_template
  if @release.nil?
    raise DirectorError, "Cannot parse template before parsing release"
  end

  template_names = safe_property(@job_spec, "template")

  if template_names.is_a?(String)
    template_names = Array(template_names)
  end

  unless template_names.is_a?(Array)
    invalid_type("template", "String or Array", template_names)
  end

  template_names.each do |template_name|
    @release.use_template_named(template_name)
    @templates << @release.template(template_name)
  end
end

#parse_update_configObject



299
300
301
302
# File 'lib/bosh/director/deployment_plan/job.rb', line 299

def parse_update_config
  update_spec = safe_property(@job_spec, "update", class: Hash, optional: true)
  @update = UpdateConfig.new(update_spec, @deployment.update)
end

#record_update_error(error, options = {}) ⇒ Object



208
209
210
211
212
213
# File 'lib/bosh/director/deployment_plan/job.rb', line 208

def record_update_error(error, options = {})
  @error_mutex.synchronize do
    @halt = true
    @halt_exception = error
  end
end

#should_halt?Boolean



204
205
206
# File 'lib/bosh/director/deployment_plan/job.rb', line 204

def should_halt?
  @halt
end

#specHash

Returns job spec as a Hash. To be used by all instances of the job to populate agent state.



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/bosh/director/deployment_plan/job.rb', line 131

def spec
  first_template = @templates[0]
  result = {
    "name" => @name,
    "release" => @release.name,
    "templates" => [],
    # --- Legacy ---
    "template" => first_template.name,
    "version" => first_template.version,
    "sha1" => first_template.sha1,
    "blobstore_id" => first_template.blobstore_id
  }
  if first_template.logs
    result["logs"] = first_template.logs
  end
  # --- /Legacy ---

  @templates.each do |template|
    template_entry = {
      "name" => template.name,
      "version" => template.version,
      "sha1" => template.sha1,
      "blobstore_id" => template.blobstore_id
    }
    if template.logs
      template_entry["logs"] = template.logs
    end
    result["templates"] << template_entry
  end

  result
end

#use_compiled_package(compiled_package_model) ⇒ void

This method returns an undefined value.

Registers compiled package with this job.



199
200
201
202
# File 'lib/bosh/director/deployment_plan/job.rb', line 199

def use_compiled_package(compiled_package_model)
  compiled_package = CompiledPackage.new(compiled_package_model)
  @packages[compiled_package.name] = compiled_package
end