bosh-director 1.5.0.pre.1113
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/CHANGELOG +34 -0
- data/bin/bosh-director +36 -0
- data/bin/bosh-director-console +84 -0
- data/bin/bosh-director-drain-workers +42 -0
- data/bin/bosh-director-migrate +58 -0
- data/bin/bosh-director-scheduler +27 -0
- data/bin/bosh-director-worker +76 -0
- data/db/migrations/README +1 -0
- data/db/migrations/director/20110209010747_initial.rb +118 -0
- data/db/migrations/director/20110406055800_add_task_user.rb +9 -0
- data/db/migrations/director/20110518225809_remove_cid_constrain.rb +13 -0
- data/db/migrations/director/20110617211923_add_deployments_release_versions.rb +32 -0
- data/db/migrations/director/20110622212607_add_task_checkpoint_timestamp.rb +9 -0
- data/db/migrations/director/20110628023039_add_state_to_instances.rb +21 -0
- data/db/migrations/director/20110709012332_add_disk_size_to_instances.rb +9 -0
- data/db/migrations/director/20110906183441_add_log_bundles.rb +11 -0
- data/db/migrations/director/20110907194830_add_logs_json_to_templates.rb +9 -0
- data/db/migrations/director/20110915205610_add_persistent_disks.rb +51 -0
- data/db/migrations/director/20111005180929_add_properties.rb +14 -0
- data/db/migrations/director/20111110024617_add_deployment_problems.rb +24 -0
- data/db/migrations/director/20111216214145_recreate_support_for_vms.rb +9 -0
- data/db/migrations/director/20120102084027_add_credentials_to_vms.rb +7 -0
- data/db/migrations/director/20120427235217_allow_multiple_releases_per_deployment.rb +36 -0
- data/db/migrations/director/20120524175805_add_task_type.rb +44 -0
- data/db/migrations/director/20120614001930_delete_redundant_deployment_release_relation.rb +34 -0
- data/db/migrations/director/20120822004528_add_fingerprint_to_templates_and_packages.rb +17 -0
- data/db/migrations/director/20120830191244_add_properties_to_templates.rb +9 -0
- data/db/migrations/director/20121106190739_persist_vm_env.rb +9 -0
- data/db/migrations/director/20130222232131_add_sha1_to_stemcells.rb +9 -0
- data/db/migrations/director/20130312211407_add_commit_hash_to_release_versions.rb +19 -0
- data/db/migrations/director/20130409235338_snapshot.rb +15 -0
- data/db/migrations/director/20130530164918_add_paused_flag_to_instance.rb +14 -0
- data/db/migrations/director/20130531172604_add_director_attributes.rb +13 -0
- data/db/migrations/dns/20120123234908_initial.rb +27 -0
- data/lib/bosh/director.rb +133 -0
- data/lib/bosh/director/agent_client.rb +78 -0
- data/lib/bosh/director/api.rb +29 -0
- data/lib/bosh/director/api/api_helper.rb +81 -0
- data/lib/bosh/director/api/backup_manager.rb +15 -0
- data/lib/bosh/director/api/controller.rb +639 -0
- data/lib/bosh/director/api/controller_helpers.rb +34 -0
- data/lib/bosh/director/api/deployment_lookup.rb +13 -0
- data/lib/bosh/director/api/deployment_manager.rb +60 -0
- data/lib/bosh/director/api/http_constants.rb +16 -0
- data/lib/bosh/director/api/instance_lookup.rb +44 -0
- data/lib/bosh/director/api/instance_manager.rb +63 -0
- data/lib/bosh/director/api/problem_manager.rb +40 -0
- data/lib/bosh/director/api/property_manager.rb +69 -0
- data/lib/bosh/director/api/release_manager.rb +59 -0
- data/lib/bosh/director/api/resource_manager.rb +69 -0
- data/lib/bosh/director/api/resurrector_manager.rb +15 -0
- data/lib/bosh/director/api/snapshot_manager.rb +94 -0
- data/lib/bosh/director/api/stemcell_manager.rb +50 -0
- data/lib/bosh/director/api/task_helper.rb +46 -0
- data/lib/bosh/director/api/task_manager.rb +64 -0
- data/lib/bosh/director/api/user_manager.rb +72 -0
- data/lib/bosh/director/api/vm_state_manager.rb +11 -0
- data/lib/bosh/director/app.rb +35 -0
- data/lib/bosh/director/blob_util.rb +87 -0
- data/lib/bosh/director/blobstores.rb +29 -0
- data/lib/bosh/director/client.rb +156 -0
- data/lib/bosh/director/cloudcheck_helper.rb +204 -0
- data/lib/bosh/director/compile_task.rb +157 -0
- data/lib/bosh/director/config.rb +370 -0
- data/lib/bosh/director/configuration_hasher.rb +114 -0
- data/lib/bosh/director/cycle_helper.rb +36 -0
- data/lib/bosh/director/db_backup.rb +22 -0
- data/lib/bosh/director/db_backup/adapter.rb +3 -0
- data/lib/bosh/director/db_backup/adapter/mysql2.rb +27 -0
- data/lib/bosh/director/db_backup/adapter/postgres.rb +36 -0
- data/lib/bosh/director/db_backup/adapter/sqlite.rb +17 -0
- data/lib/bosh/director/db_backup/error.rb +10 -0
- data/lib/bosh/director/deployment_plan.rb +26 -0
- data/lib/bosh/director/deployment_plan/assembler.rb +430 -0
- data/lib/bosh/director/deployment_plan/compilation_config.rb +54 -0
- data/lib/bosh/director/deployment_plan/compiled_package.rb +35 -0
- data/lib/bosh/director/deployment_plan/dynamic_network.rb +91 -0
- data/lib/bosh/director/deployment_plan/idle_vm.rb +109 -0
- data/lib/bosh/director/deployment_plan/instance.rb +413 -0
- data/lib/bosh/director/deployment_plan/job.rb +470 -0
- data/lib/bosh/director/deployment_plan/manual_network.rb +137 -0
- data/lib/bosh/director/deployment_plan/network.rb +74 -0
- data/lib/bosh/director/deployment_plan/network_subnet.rb +167 -0
- data/lib/bosh/director/deployment_plan/planner.rb +288 -0
- data/lib/bosh/director/deployment_plan/preparer.rb +52 -0
- data/lib/bosh/director/deployment_plan/release.rb +126 -0
- data/lib/bosh/director/deployment_plan/resource_pool.rb +143 -0
- data/lib/bosh/director/deployment_plan/resource_pools.rb +68 -0
- data/lib/bosh/director/deployment_plan/stemcell.rb +56 -0
- data/lib/bosh/director/deployment_plan/template.rb +94 -0
- data/lib/bosh/director/deployment_plan/update_config.rb +80 -0
- data/lib/bosh/director/deployment_plan/updater.rb +55 -0
- data/lib/bosh/director/deployment_plan/vip_network.rb +79 -0
- data/lib/bosh/director/dns_helper.rb +204 -0
- data/lib/bosh/director/download_helper.rb +44 -0
- data/lib/bosh/director/duration.rb +36 -0
- data/lib/bosh/director/encryption_helper.rb +10 -0
- data/lib/bosh/director/errors.rb +198 -0
- data/lib/bosh/director/event_log.rb +136 -0
- data/lib/bosh/director/ext.rb +64 -0
- data/lib/bosh/director/hash_string_vals.rb +13 -0
- data/lib/bosh/director/instance_deleter.rb +109 -0
- data/lib/bosh/director/instance_updater.rb +506 -0
- data/lib/bosh/director/ip_util.rb +67 -0
- data/lib/bosh/director/job_queue.rb +16 -0
- data/lib/bosh/director/job_runner.rb +162 -0
- data/lib/bosh/director/job_updater.rb +121 -0
- data/lib/bosh/director/jobs/backup.rb +86 -0
- data/lib/bosh/director/jobs/base_job.rb +66 -0
- data/lib/bosh/director/jobs/cloud_check/apply_resolutions.rb +46 -0
- data/lib/bosh/director/jobs/cloud_check/scan.rb +38 -0
- data/lib/bosh/director/jobs/cloud_check/scan_and_fix.rb +73 -0
- data/lib/bosh/director/jobs/create_snapshot.rb +23 -0
- data/lib/bosh/director/jobs/delete_deployment.rb +183 -0
- data/lib/bosh/director/jobs/delete_deployment_snapshots.rb +34 -0
- data/lib/bosh/director/jobs/delete_release.rb +219 -0
- data/lib/bosh/director/jobs/delete_snapshots.rb +23 -0
- data/lib/bosh/director/jobs/delete_stemcell.rb +102 -0
- data/lib/bosh/director/jobs/fetch_logs.rb +99 -0
- data/lib/bosh/director/jobs/scheduled_backup.rb +38 -0
- data/lib/bosh/director/jobs/snapshot_deployment.rb +61 -0
- data/lib/bosh/director/jobs/snapshot_deployments.rb +23 -0
- data/lib/bosh/director/jobs/snapshot_self.rb +43 -0
- data/lib/bosh/director/jobs/ssh.rb +59 -0
- data/lib/bosh/director/jobs/update_deployment.rb +110 -0
- data/lib/bosh/director/jobs/update_release.rb +672 -0
- data/lib/bosh/director/jobs/update_stemcell.rb +109 -0
- data/lib/bosh/director/jobs/vm_state.rb +89 -0
- data/lib/bosh/director/lock.rb +133 -0
- data/lib/bosh/director/lock_helper.rb +92 -0
- data/lib/bosh/director/models.rb +29 -0
- data/lib/bosh/director/models/compiled_package.rb +33 -0
- data/lib/bosh/director/models/deployment.rb +22 -0
- data/lib/bosh/director/models/deployment_problem.rb +49 -0
- data/lib/bosh/director/models/deployment_property.rb +21 -0
- data/lib/bosh/director/models/director_attribute.rb +9 -0
- data/lib/bosh/director/models/dns.rb +9 -0
- data/lib/bosh/director/models/dns/domain.rb +9 -0
- data/lib/bosh/director/models/dns/record.rb +7 -0
- data/lib/bosh/director/models/helpers/model_helper.rb +7 -0
- data/lib/bosh/director/models/instance.rb +28 -0
- data/lib/bosh/director/models/log_bundle.rb +10 -0
- data/lib/bosh/director/models/package.rb +30 -0
- data/lib/bosh/director/models/persistent_disk.rb +13 -0
- data/lib/bosh/director/models/release.rb +17 -0
- data/lib/bosh/director/models/release_version.rb +16 -0
- data/lib/bosh/director/models/snapshot.rb +13 -0
- data/lib/bosh/director/models/stemcell.rb +18 -0
- data/lib/bosh/director/models/task.rb +10 -0
- data/lib/bosh/director/models/template.rb +44 -0
- data/lib/bosh/director/models/user.rb +11 -0
- data/lib/bosh/director/models/vm.rb +42 -0
- data/lib/bosh/director/nats_rpc.rb +54 -0
- data/lib/bosh/director/network_reservation.rb +121 -0
- data/lib/bosh/director/next_rebase_version.rb +20 -0
- data/lib/bosh/director/package_compiler.rb +423 -0
- data/lib/bosh/director/problem_handlers/base.rb +153 -0
- data/lib/bosh/director/problem_handlers/inactive_disk.rb +112 -0
- data/lib/bosh/director/problem_handlers/invalid_problem.rb +28 -0
- data/lib/bosh/director/problem_handlers/missing_vm.rb +34 -0
- data/lib/bosh/director/problem_handlers/mount_info_mismatch.rb +62 -0
- data/lib/bosh/director/problem_handlers/out_of_sync_vm.rb +64 -0
- data/lib/bosh/director/problem_handlers/unbound_instance_vm.rb +85 -0
- data/lib/bosh/director/problem_handlers/unresponsive_agent.rb +78 -0
- data/lib/bosh/director/problem_resolver.rb +103 -0
- data/lib/bosh/director/problem_scanner.rb +268 -0
- data/lib/bosh/director/resource_pool_updater.rb +216 -0
- data/lib/bosh/director/scheduler.rb +57 -0
- data/lib/bosh/director/sequel.rb +13 -0
- data/lib/bosh/director/tar_gzipper.rb +47 -0
- data/lib/bosh/director/task_result_file.rb +19 -0
- data/lib/bosh/director/thread_pool.rb +8 -0
- data/lib/bosh/director/validation_helper.rb +55 -0
- data/lib/bosh/director/version.rb +7 -0
- data/lib/bosh/director/vm_creator.rb +80 -0
- data/lib/bosh/director/vm_data.rb +63 -0
- data/lib/bosh/director/vm_metadata_updater.rb +29 -0
- data/lib/bosh/director/vm_reuser.rb +63 -0
- data/lib/cloud/dummy.rb +149 -0
- metadata +664 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Bosh::Director
|
|
2
|
+
module Jobs
|
|
3
|
+
class SnapshotDeployment < BaseJob
|
|
4
|
+
@queue = :normal
|
|
5
|
+
|
|
6
|
+
attr_reader :deployment
|
|
7
|
+
|
|
8
|
+
def self.job_type
|
|
9
|
+
:snapshot_deployment
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(deployment_name, options = {})
|
|
13
|
+
@deployment = deployment_manager.find_by_name(deployment_name)
|
|
14
|
+
@options = options
|
|
15
|
+
@errors = 0
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def deployment_manager
|
|
19
|
+
@deployment_manager ||= Bosh::Director::Api::DeploymentManager.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def perform
|
|
23
|
+
logger.info("taking snapshot of: #{deployment.name}")
|
|
24
|
+
deployment.job_instances.each do |instance|
|
|
25
|
+
snapshot(instance)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
msg = "snapshots of deployment '#{deployment.name}' created"
|
|
29
|
+
msg += ", with #{@errors} failure(s)" unless @errors == 0
|
|
30
|
+
msg
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def snapshot(instance)
|
|
34
|
+
logger.info("taking snapshot of: #{instance.job}/#{instance.index} (#{instance.vm.cid})")
|
|
35
|
+
Bosh::Director::Api::SnapshotManager.take_snapshot(instance, @options)
|
|
36
|
+
rescue Bosh::Clouds::CloudError => e
|
|
37
|
+
@errors += 1
|
|
38
|
+
logger.error("failed to take snapshot of: #{instance.job}/#{instance.index} (#{instance.vm.cid}) - #{e.inspect}")
|
|
39
|
+
send_alert(instance, e.inspect)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
ERROR = 3
|
|
43
|
+
|
|
44
|
+
def send_alert(instance, message)
|
|
45
|
+
nats = Bosh::Director::Config.nats
|
|
46
|
+
payload = Yajl::Encoder.encode(
|
|
47
|
+
{
|
|
48
|
+
"id" => 'director',
|
|
49
|
+
"severity" => ERROR,
|
|
50
|
+
"title" => "director - snapshot failure",
|
|
51
|
+
"summary" => "failed to snapshot #{instance.job}/#{instance.index}: #{message}",
|
|
52
|
+
"created_at" => Time.now.to_i
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
nats.publish('hm.director.alert', payload)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Bosh::Director
|
|
2
|
+
module Jobs
|
|
3
|
+
class SnapshotDeployments < BaseJob
|
|
4
|
+
@queue = :normal
|
|
5
|
+
|
|
6
|
+
def self.job_type
|
|
7
|
+
:snapshot_deployments
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(options={})
|
|
11
|
+
@snapshot_manager = options.fetch(:snapshot_manager) { Bosh::Director::Api::SnapshotManager.new }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def perform
|
|
15
|
+
tasks = Models::Deployment.all.map do |deployment|
|
|
16
|
+
@snapshot_manager.create_deployment_snapshot_task('scheduler', deployment)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
"Enqueued snapshot tasks [#{tasks.map(&:id).join(', ')}]"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Bosh::Director
|
|
2
|
+
module Jobs
|
|
3
|
+
class SnapshotSelf < BaseJob
|
|
4
|
+
@queue = :normal
|
|
5
|
+
|
|
6
|
+
def self.job_type
|
|
7
|
+
:snapshot_self
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(options={})
|
|
11
|
+
@cloud = options.fetch(:cloud) { Config.cloud }
|
|
12
|
+
@director_uuid = options.fetch(:director_uuid) { Config.uuid }
|
|
13
|
+
@director_name = options.fetch(:director_name) { Config.name }
|
|
14
|
+
@enable_snapshots = options.fetch(:enable_snapshots) { Config.enable_snapshots }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def perform
|
|
18
|
+
unless @enable_snapshots
|
|
19
|
+
logger.info('Snapshots are disabled; skipping')
|
|
20
|
+
return
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
vm_id = @cloud.current_vm_id
|
|
24
|
+
disks = @cloud.get_disks(vm_id)
|
|
25
|
+
metadata = {
|
|
26
|
+
deployment: 'self',
|
|
27
|
+
job: 'director',
|
|
28
|
+
index: 0,
|
|
29
|
+
director_name: @director_name,
|
|
30
|
+
director_uuid: @director_uuid,
|
|
31
|
+
agent_id: 'self',
|
|
32
|
+
instance_id: vm_id
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
disks.each { |disk| @cloud.snapshot_disk(disk, metadata) }
|
|
36
|
+
|
|
37
|
+
"Snapshot director disks [#{disks.join(', ')}]"
|
|
38
|
+
rescue Bosh::Clouds::NotImplemented
|
|
39
|
+
logger.info('CPI does not support disk snapshots; skipping')
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh::Director
|
|
4
|
+
module Jobs
|
|
5
|
+
class Ssh < BaseJob
|
|
6
|
+
DEFAULT_SSH_DATA_LIFETIME = 300
|
|
7
|
+
SSH_TAG = "ssh"
|
|
8
|
+
@queue = :normal
|
|
9
|
+
|
|
10
|
+
def self.job_type
|
|
11
|
+
:ssh
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(deployment_id, options = {})
|
|
15
|
+
@deployment_id = deployment_id
|
|
16
|
+
@target = options["target"]
|
|
17
|
+
@command = options["command"]
|
|
18
|
+
@params = options["params"]
|
|
19
|
+
@blobstore = options.fetch(:blobstore) { App.instance.blobstores.blobstore }
|
|
20
|
+
@instance_manager = Api::InstanceManager.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def perform
|
|
24
|
+
job = @target["job"]
|
|
25
|
+
indexes = @target["indexes"]
|
|
26
|
+
|
|
27
|
+
filter = {
|
|
28
|
+
:deployment_id => @deployment_id
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if indexes && indexes.size > 0
|
|
32
|
+
filter[:index] = indexes
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if job
|
|
36
|
+
filter[:job] = job
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
instances = @instance_manager.filter_by(filter)
|
|
40
|
+
|
|
41
|
+
ssh_info = instances.map do |instance|
|
|
42
|
+
agent = @instance_manager.agent_client_for(instance)
|
|
43
|
+
|
|
44
|
+
logger.info("ssh #{@command} `#{instance.job}/#{instance.index}'")
|
|
45
|
+
result = agent.ssh(@command, @params)
|
|
46
|
+
result["index"] = instance.index
|
|
47
|
+
|
|
48
|
+
result
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
result_file.write(Yajl::Encoder.encode(ssh_info))
|
|
52
|
+
result_file.write("\n")
|
|
53
|
+
|
|
54
|
+
# task result
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh::Director
|
|
4
|
+
module Jobs
|
|
5
|
+
class UpdateDeployment < BaseJob
|
|
6
|
+
include LockHelper
|
|
7
|
+
|
|
8
|
+
@queue = :normal
|
|
9
|
+
|
|
10
|
+
def self.job_type
|
|
11
|
+
:update_deployment
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @param [String] manifest_file Path to deployment manifest
|
|
15
|
+
# @param [Hash] options Deployment options
|
|
16
|
+
def initialize(manifest_file, options = {})
|
|
17
|
+
logger.info('Reading deployment manifest')
|
|
18
|
+
@manifest_file = manifest_file
|
|
19
|
+
@manifest = File.read(@manifest_file)
|
|
20
|
+
logger.debug("Manifest:\n#{@manifest}")
|
|
21
|
+
|
|
22
|
+
logger.info('Creating deployment plan')
|
|
23
|
+
logger.info("Deployment plan options: #{options.pretty_inspect}")
|
|
24
|
+
|
|
25
|
+
plan_options = {
|
|
26
|
+
'recreate' => !!options['recreate'],
|
|
27
|
+
'job_states' => options['job_states'] || {},
|
|
28
|
+
'job_rename' => options['job_rename'] || {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
manifest_as_hash = Psych.load(@manifest)
|
|
32
|
+
@deployment_plan = DeploymentPlan::Planner.new(manifest_as_hash, plan_options)
|
|
33
|
+
@deployment_plan.parse
|
|
34
|
+
logger.info('Created deployment plan')
|
|
35
|
+
|
|
36
|
+
resource_pools = @deployment_plan.resource_pools
|
|
37
|
+
@resource_pool_updaters = resource_pools.map do |resource_pool|
|
|
38
|
+
ResourcePoolUpdater.new(resource_pool)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def prepare
|
|
43
|
+
@assembler = DeploymentPlan::Assembler.new(@deployment_plan)
|
|
44
|
+
preparer = DeploymentPlan::Preparer.new(self, @assembler)
|
|
45
|
+
|
|
46
|
+
begin_stage('Preparing deployment', 9)
|
|
47
|
+
preparer.prepare
|
|
48
|
+
|
|
49
|
+
logger.info('Compiling and binding packages')
|
|
50
|
+
PackageCompiler.new(@deployment_plan).compile
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def update
|
|
54
|
+
resource_pools = DeploymentPlan::ResourcePools.new(event_log, @resource_pool_updaters)
|
|
55
|
+
|
|
56
|
+
updater = DeploymentPlan::Updater.new(self, event_log, resource_pools, @assembler, @deployment_plan)
|
|
57
|
+
updater.update
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def update_stemcell_references
|
|
61
|
+
current_stemcells = Set.new
|
|
62
|
+
@deployment_plan.resource_pools.each do |resource_pool|
|
|
63
|
+
current_stemcells << resource_pool.stemcell.model
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
deployment = @deployment_plan.model
|
|
67
|
+
stemcells = deployment.stemcells
|
|
68
|
+
stemcells.each do |stemcell|
|
|
69
|
+
unless current_stemcells.include?(stemcell)
|
|
70
|
+
stemcell.remove_deployment(deployment)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def perform
|
|
76
|
+
with_deployment_lock(@deployment_plan) do
|
|
77
|
+
logger.info('Preparing deployment')
|
|
78
|
+
prepare
|
|
79
|
+
begin
|
|
80
|
+
deployment = @deployment_plan.model
|
|
81
|
+
logger.info('Finished preparing deployment')
|
|
82
|
+
logger.info('Updating deployment')
|
|
83
|
+
update
|
|
84
|
+
|
|
85
|
+
with_release_locks(@deployment_plan) do
|
|
86
|
+
deployment.db.transaction do
|
|
87
|
+
deployment.remove_all_release_versions
|
|
88
|
+
# Now we know that deployment has succeeded and can remove
|
|
89
|
+
# previous partial deployments release version references
|
|
90
|
+
# to be able to delete these release versions later.
|
|
91
|
+
@deployment_plan.releases.each do |release|
|
|
92
|
+
deployment.add_release_version(release.model)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
deployment.manifest = @manifest
|
|
98
|
+
deployment.save
|
|
99
|
+
logger.info('Finished updating deployment')
|
|
100
|
+
"/deployments/#{deployment.name}"
|
|
101
|
+
ensure
|
|
102
|
+
update_stemcell_references
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
ensure
|
|
106
|
+
FileUtils.rm_rf(@manifest_file)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
require 'common/version_number'
|
|
4
|
+
|
|
5
|
+
module Bosh::Director
|
|
6
|
+
module Jobs
|
|
7
|
+
class UpdateRelease < BaseJob
|
|
8
|
+
include LockHelper
|
|
9
|
+
include DownloadHelper
|
|
10
|
+
|
|
11
|
+
@queue = :normal
|
|
12
|
+
|
|
13
|
+
attr_accessor :release_model
|
|
14
|
+
attr_accessor :tmp_release_dir
|
|
15
|
+
|
|
16
|
+
def self.job_type
|
|
17
|
+
:update_release
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param [String] tmp_release_dir Directory containing release bundle
|
|
21
|
+
# @param [Hash] options Release update options
|
|
22
|
+
def initialize(tmp_release_dir, options = {})
|
|
23
|
+
@tmp_release_dir = tmp_release_dir
|
|
24
|
+
@release_model = nil
|
|
25
|
+
@release_version_model = nil
|
|
26
|
+
|
|
27
|
+
@rebase = !!options["rebase"]
|
|
28
|
+
@package_rebase_mapping = {}
|
|
29
|
+
@job_rebase_mapping = {}
|
|
30
|
+
|
|
31
|
+
@manifest = nil
|
|
32
|
+
@name = nil
|
|
33
|
+
@version = nil
|
|
34
|
+
|
|
35
|
+
@packages_unchanged = false
|
|
36
|
+
@jobs_unchanged = false
|
|
37
|
+
|
|
38
|
+
@remote_release = options['remote'] || false
|
|
39
|
+
@remote_release_location = options['location'] if @remote_release
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Extracts release tarball, verifies release manifest and saves release
|
|
43
|
+
# in DB
|
|
44
|
+
# @return [void]
|
|
45
|
+
def perform
|
|
46
|
+
logger.info("Processing update release")
|
|
47
|
+
if @rebase
|
|
48
|
+
logger.info("Release rebase will be performed")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
single_step_stage("Downloading remote release") { download_remote_release } if @remote_release
|
|
52
|
+
single_step_stage("Extracting release") { extract_release }
|
|
53
|
+
single_step_stage("Verifying manifest") { verify_manifest }
|
|
54
|
+
|
|
55
|
+
with_release_lock(@name) { process_release }
|
|
56
|
+
|
|
57
|
+
if @rebase && @packages_unchanged && @jobs_unchanged
|
|
58
|
+
raise DirectorError,
|
|
59
|
+
"Rebase is attempted without any job or package changes"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
"Created release `#{@name}/#{@version}'"
|
|
63
|
+
rescue Exception => e
|
|
64
|
+
remove_release_version_model
|
|
65
|
+
raise e
|
|
66
|
+
ensure
|
|
67
|
+
if @tmp_release_dir && File.exists?(@tmp_release_dir)
|
|
68
|
+
FileUtils.rm_rf(@tmp_release_dir)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def download_remote_release
|
|
73
|
+
release_file = File.join(@tmp_release_dir, Api::ReleaseManager::RELEASE_TGZ)
|
|
74
|
+
download_remote_file('release', @remote_release_location, release_file)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Extracts release tarball
|
|
78
|
+
# @return [void]
|
|
79
|
+
def extract_release
|
|
80
|
+
release_tgz = File.join(@tmp_release_dir,
|
|
81
|
+
Api::ReleaseManager::RELEASE_TGZ)
|
|
82
|
+
|
|
83
|
+
result = Bosh::Exec.sh("tar -C #{@tmp_release_dir} -xzf #{release_tgz} 2>&1", :on_error => :return)
|
|
84
|
+
if result.failed?
|
|
85
|
+
logger.error("Extracting release archive failed in dir #{@tmp_release_dir}, " +
|
|
86
|
+
"tar returned #{result.exit_status}, " +
|
|
87
|
+
"output: #{result.output}")
|
|
88
|
+
raise ReleaseInvalidArchive, "Extracting release archive failed. Check task debug log for details."
|
|
89
|
+
end
|
|
90
|
+
ensure
|
|
91
|
+
if release_tgz && File.exists?(release_tgz)
|
|
92
|
+
FileUtils.rm(release_tgz)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @return [void]
|
|
97
|
+
def verify_manifest
|
|
98
|
+
manifest_file = File.join(@tmp_release_dir, "release.MF")
|
|
99
|
+
unless File.file?(manifest_file)
|
|
100
|
+
raise ReleaseManifestNotFound, "Release manifest not found"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
@manifest = Psych.load_file(manifest_file)
|
|
104
|
+
normalize_manifest
|
|
105
|
+
|
|
106
|
+
@name = @manifest["name"]
|
|
107
|
+
@version = @manifest["version"]
|
|
108
|
+
@commit_hash = @manifest.fetch("commit_hash", nil)
|
|
109
|
+
@uncommitted_changes = @manifest.fetch("uncommitted_changes", nil)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Processes uploaded release, creates jobs and packages in DB if needed
|
|
113
|
+
# @return [void]
|
|
114
|
+
def process_release
|
|
115
|
+
@release_model = Models::Release.find_or_create(:name => @name)
|
|
116
|
+
if @rebase
|
|
117
|
+
@version = next_release_version
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
version_attrs = {
|
|
121
|
+
:release => @release_model,
|
|
122
|
+
:version => @version
|
|
123
|
+
}
|
|
124
|
+
version_attrs[:uncommitted_changes] = @uncommitted_changes if @uncommitted_changes
|
|
125
|
+
version_attrs[:commit_hash] = @commit_hash if @commit_hash
|
|
126
|
+
|
|
127
|
+
@release_version_model = Models::ReleaseVersion.new(version_attrs)
|
|
128
|
+
unless @release_version_model.valid?
|
|
129
|
+
raise ReleaseAlreadyExists,
|
|
130
|
+
"Release `#{@name}/#{@version}' already exists"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
@release_version_model.save
|
|
134
|
+
|
|
135
|
+
single_step_stage("Resolving package dependencies") do
|
|
136
|
+
resolve_package_dependencies(@manifest["packages"])
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
@packages = {}
|
|
140
|
+
process_packages
|
|
141
|
+
process_jobs
|
|
142
|
+
|
|
143
|
+
unless @package_rebase_mapping.empty?
|
|
144
|
+
event_log.begin_stage(
|
|
145
|
+
"Rebased packages", @package_rebase_mapping.size)
|
|
146
|
+
@package_rebase_mapping.each_pair do |name, transition|
|
|
147
|
+
event_log.track("#{name}: #{transition}") {}
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
unless @job_rebase_mapping.empty?
|
|
152
|
+
event_log.begin_stage(
|
|
153
|
+
"Rebased jobs", @job_rebase_mapping.size)
|
|
154
|
+
@job_rebase_mapping.each_pair do |name, transition|
|
|
155
|
+
event_log.track("#{name}: #{transition}") {}
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
event_log.begin_stage("Release has been created", 1)
|
|
160
|
+
event_log.track("#{@name}/#{@version}") {}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Normalizes release manifest, so all names, versions, and checksums
|
|
164
|
+
# are Strings.
|
|
165
|
+
# @return [void]
|
|
166
|
+
def normalize_manifest
|
|
167
|
+
Bosh::Director.hash_string_vals(@manifest, 'name', 'version')
|
|
168
|
+
|
|
169
|
+
@manifest['packages'].each { |p| Bosh::Director.hash_string_vals(p, 'name', 'version', 'sha1') }
|
|
170
|
+
@manifest['jobs'].each { |j| Bosh::Director.hash_string_vals(j, 'name', 'version', 'sha1') }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Resolves package dependencies, makes sure there are no cycles
|
|
174
|
+
# and all dependencies are present
|
|
175
|
+
# @return [void]
|
|
176
|
+
def resolve_package_dependencies(packages)
|
|
177
|
+
packages_by_name = {}
|
|
178
|
+
packages.each do |package|
|
|
179
|
+
packages_by_name[package["name"]] = package
|
|
180
|
+
package["dependencies"] ||= []
|
|
181
|
+
end
|
|
182
|
+
dependency_lookup = lambda do |package_name|
|
|
183
|
+
packages_by_name[package_name]["dependencies"]
|
|
184
|
+
end
|
|
185
|
+
result = CycleHelper.check_for_cycle(packages_by_name.keys,
|
|
186
|
+
:connected_vertices => true,
|
|
187
|
+
&dependency_lookup)
|
|
188
|
+
|
|
189
|
+
packages.each do |package|
|
|
190
|
+
name = package["name"]
|
|
191
|
+
dependencies = package["dependencies"]
|
|
192
|
+
|
|
193
|
+
logger.info("Resolving package dependencies for `#{name}', " +
|
|
194
|
+
"found: #{dependencies.pretty_inspect}")
|
|
195
|
+
package["dependencies"] = result[:connected_vertices][name]
|
|
196
|
+
logger.info("Resolved package dependencies for `#{name}', " +
|
|
197
|
+
"to: #{dependencies.pretty_inspect}")
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Finds all package definitions in the manifest and sorts them into two
|
|
202
|
+
# buckets: new and existing packages, then creates new packages and points
|
|
203
|
+
# current release version to the existing packages.
|
|
204
|
+
# @return [void]
|
|
205
|
+
def process_packages
|
|
206
|
+
logger.info("Checking for new packages in release")
|
|
207
|
+
|
|
208
|
+
new_packages = []
|
|
209
|
+
existing_packages = []
|
|
210
|
+
|
|
211
|
+
@manifest["packages"].each do |package_meta|
|
|
212
|
+
filter = {:sha1 => package_meta["sha1"]}
|
|
213
|
+
if package_meta["fingerprint"]
|
|
214
|
+
filter[:fingerprint] = package_meta["fingerprint"]
|
|
215
|
+
filter = filter.sql_or
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Checking whether we might have the same bits somewhere
|
|
219
|
+
packages = Models::Package.where(filter).all
|
|
220
|
+
|
|
221
|
+
if packages.empty?
|
|
222
|
+
new_packages << package_meta
|
|
223
|
+
next
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Rebase is an interesting use case: we don't really care about
|
|
227
|
+
# preserving the original package/job versions, so if we have a
|
|
228
|
+
# checksum/fingerprint match, we can just substitute the original
|
|
229
|
+
# package/job version with an existing one.
|
|
230
|
+
if @rebase
|
|
231
|
+
substitutes = packages.select do |package|
|
|
232
|
+
package.release_id == @release_model.id &&
|
|
233
|
+
package.name == package_meta["name"] &&
|
|
234
|
+
package.dependency_set == Set.new(package_meta["dependencies"])
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
substitute = pick_best(substitutes, package_meta["version"])
|
|
238
|
+
|
|
239
|
+
if substitute
|
|
240
|
+
package_meta["version"] = substitute.version
|
|
241
|
+
package_meta["sha1"] = substitute.sha1
|
|
242
|
+
existing_packages << [substitute, package_meta]
|
|
243
|
+
next
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# We can reuse an existing package as long as it
|
|
248
|
+
# belongs to the same release and has the same name and version.
|
|
249
|
+
existing_package = packages.find do |package|
|
|
250
|
+
package.release_id == @release_model.id &&
|
|
251
|
+
package.name == package_meta["name"] &&
|
|
252
|
+
package.version == package_meta["version"]
|
|
253
|
+
# NOT checking dependencies here b/c dependency change would
|
|
254
|
+
# bump the package version anyway.
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
if existing_package
|
|
258
|
+
existing_packages << [existing_package, package_meta]
|
|
259
|
+
else
|
|
260
|
+
# We found a package with the same checksum but different
|
|
261
|
+
# (release, name, version) tuple, so we need to make a copy
|
|
262
|
+
# of the package blob and create a new db entry for it
|
|
263
|
+
package = packages.first
|
|
264
|
+
package_meta["blobstore_id"] = package.blobstore_id
|
|
265
|
+
new_packages << package_meta
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
create_packages(new_packages)
|
|
270
|
+
use_existing_packages(existing_packages)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Creates packages using provided metadata
|
|
274
|
+
# @param [Array<Hash>] packages Packages metadata
|
|
275
|
+
# @return [void]
|
|
276
|
+
def create_packages(packages)
|
|
277
|
+
if packages.empty?
|
|
278
|
+
@packages_unchanged = true
|
|
279
|
+
return
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
event_log.begin_stage("Creating new packages", packages.size)
|
|
283
|
+
packages.each do |package_meta|
|
|
284
|
+
package_desc = "#{package_meta["name"]}/#{package_meta["version"]}"
|
|
285
|
+
event_log.track(package_desc) do
|
|
286
|
+
logger.info("Creating new package `#{package_desc}'")
|
|
287
|
+
package = create_package(package_meta)
|
|
288
|
+
register_package(package)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Points release DB model to existing packages described by given metadata
|
|
294
|
+
# @param [Array<Array>] packages Existing packages metadata
|
|
295
|
+
def use_existing_packages(packages)
|
|
296
|
+
return if packages.empty?
|
|
297
|
+
|
|
298
|
+
n_packages = packages.size
|
|
299
|
+
event_log.begin_stage("Processing #{n_packages} existing " +
|
|
300
|
+
"package#{n_packages > 1 ? "s" : ""}", 1)
|
|
301
|
+
|
|
302
|
+
event_log.track("Verifying checksums") do
|
|
303
|
+
packages.each do |package, package_meta|
|
|
304
|
+
package_desc = "#{package.name}/#{package.version}"
|
|
305
|
+
logger.info("Package `#{package_desc}' already exists, " +
|
|
306
|
+
"verifying checksum")
|
|
307
|
+
|
|
308
|
+
expected = package.sha1
|
|
309
|
+
received = package_meta["sha1"]
|
|
310
|
+
|
|
311
|
+
if expected != received
|
|
312
|
+
raise ReleaseExistingPackageHashMismatch,
|
|
313
|
+
"`#{package_desc}' checksum mismatch, " +
|
|
314
|
+
"expected #{expected} but received #{received}"
|
|
315
|
+
end
|
|
316
|
+
logger.info("Package `#{package_desc}' verified")
|
|
317
|
+
register_package(package)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Creates package in DB according to given metadata
|
|
323
|
+
# @param [Hash] package_meta Package metadata
|
|
324
|
+
# @return [void]
|
|
325
|
+
def create_package(package_meta)
|
|
326
|
+
name, version = package_meta["name"], package_meta["version"]
|
|
327
|
+
|
|
328
|
+
package_attrs = {
|
|
329
|
+
:release => @release_model,
|
|
330
|
+
:name => name,
|
|
331
|
+
:sha1 => package_meta["sha1"],
|
|
332
|
+
:fingerprint => package_meta["fingerprint"],
|
|
333
|
+
:version => version
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if @rebase
|
|
337
|
+
new_version = next_package_version(name, version)
|
|
338
|
+
if new_version != version
|
|
339
|
+
transition = "#{version} -> #{new_version}"
|
|
340
|
+
logger.info("Package `#{name}' rebased: #{transition}")
|
|
341
|
+
package_attrs[:version] = new_version
|
|
342
|
+
version = new_version
|
|
343
|
+
@package_rebase_mapping[name] = transition
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
package = Models::Package.new(package_attrs)
|
|
348
|
+
package.dependency_set = package_meta["dependencies"]
|
|
349
|
+
|
|
350
|
+
existing_blob = package_meta["blobstore_id"]
|
|
351
|
+
desc = "package `#{name}/#{version}'"
|
|
352
|
+
|
|
353
|
+
if existing_blob
|
|
354
|
+
logger.info("Creating #{desc} from existing blob #{existing_blob}")
|
|
355
|
+
package.blobstore_id = BlobUtil.copy_blob(existing_blob)
|
|
356
|
+
else
|
|
357
|
+
logger.info("Creating #{desc} from provided bits")
|
|
358
|
+
|
|
359
|
+
package_tgz = File.join(@tmp_release_dir, "packages", "#{name}.tgz")
|
|
360
|
+
result = Bosh::Exec.sh("tar -tzf #{package_tgz} 2>&1", :on_error => :return)
|
|
361
|
+
if result.failed?
|
|
362
|
+
logger.error("Extracting #{desc} archive failed, " +
|
|
363
|
+
"tar returned #{result.exit_status}, " +
|
|
364
|
+
"output: #{result.output}")
|
|
365
|
+
raise PackageInvalidArchive, "Extracting #{desc} archive failed. Check task debug log for details."
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
package.blobstore_id = BlobUtil.create_blob(package_tgz)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
package.save
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Marks package model as used by release version model
|
|
375
|
+
# @param [Models::Package] package Package model
|
|
376
|
+
# @return [void]
|
|
377
|
+
def register_package(package)
|
|
378
|
+
@packages[package.name] = package
|
|
379
|
+
@release_version_model.add_package(package)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# Finds job template definitions in release manifest and sorts them into
|
|
383
|
+
# two buckets: new and existing job templates, then creates new job
|
|
384
|
+
# template records in the database and points release version to existing
|
|
385
|
+
# ones.
|
|
386
|
+
# @return [void]
|
|
387
|
+
def process_jobs
|
|
388
|
+
logger.info("Checking for new jobs in release")
|
|
389
|
+
|
|
390
|
+
new_jobs = []
|
|
391
|
+
existing_jobs = []
|
|
392
|
+
|
|
393
|
+
@manifest["jobs"].each do |job_meta|
|
|
394
|
+
filter = {:sha1 => job_meta["sha1"]}
|
|
395
|
+
if job_meta["fingerprint"]
|
|
396
|
+
filter[:fingerprint] = job_meta["fingerprint"]
|
|
397
|
+
filter = filter.sql_or
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Checking whether we might have the same bits somewhere
|
|
401
|
+
jobs = Models::Template.where(filter).all
|
|
402
|
+
|
|
403
|
+
if @rebase
|
|
404
|
+
substitutes = jobs.select do |job|
|
|
405
|
+
job.release_id == @release_model.id &&
|
|
406
|
+
job.name == job_meta["name"]
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
substitute = pick_best(substitutes, job_meta["version"])
|
|
410
|
+
|
|
411
|
+
if substitute
|
|
412
|
+
job_meta["version"] = substitute.version
|
|
413
|
+
job_meta["sha1"] = substitute.sha1
|
|
414
|
+
existing_jobs << [substitute, job_meta]
|
|
415
|
+
else
|
|
416
|
+
new_jobs << job_meta
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
next
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
template = jobs.find do |job|
|
|
423
|
+
job.release_id == @release_model.id &&
|
|
424
|
+
job.name == job_meta["name"] &&
|
|
425
|
+
job.version == job_meta["version"]
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
if template.nil?
|
|
429
|
+
new_jobs << job_meta
|
|
430
|
+
else
|
|
431
|
+
existing_jobs << [template, job_meta]
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
create_jobs(new_jobs)
|
|
436
|
+
use_existing_jobs(existing_jobs)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def create_jobs(jobs)
|
|
440
|
+
if jobs.empty?
|
|
441
|
+
@jobs_unchanged = true
|
|
442
|
+
return
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
event_log.begin_stage("Creating new jobs", jobs.size)
|
|
446
|
+
jobs.each do |job_meta|
|
|
447
|
+
job_desc = "#{job_meta["name"]}/#{job_meta["version"]}"
|
|
448
|
+
event_log.track(job_desc) do
|
|
449
|
+
logger.info("Creating new template `#{job_desc}'")
|
|
450
|
+
template = create_job(job_meta)
|
|
451
|
+
register_template(template)
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def create_job(job_meta)
|
|
457
|
+
name, version = job_meta["name"], job_meta["version"]
|
|
458
|
+
|
|
459
|
+
template_attrs = {
|
|
460
|
+
:release => @release_model,
|
|
461
|
+
:name => name,
|
|
462
|
+
:sha1 => job_meta["sha1"],
|
|
463
|
+
:fingerprint => job_meta["fingerprint"],
|
|
464
|
+
:version => version
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if @rebase
|
|
468
|
+
new_version = next_template_version(name, version)
|
|
469
|
+
if new_version != version
|
|
470
|
+
transition = "#{version} -> #{new_version}"
|
|
471
|
+
logger.info("Job `#{name}' rebased: #{transition}")
|
|
472
|
+
template_attrs[:version] = new_version
|
|
473
|
+
version = new_version
|
|
474
|
+
@job_rebase_mapping[name] = transition
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
logger.info("Creating job template `#{name}/#{version}' " +
|
|
479
|
+
"from provided bits")
|
|
480
|
+
template = Models::Template.new(template_attrs)
|
|
481
|
+
|
|
482
|
+
job_tgz = File.join(@tmp_release_dir, "jobs", "#{name}.tgz")
|
|
483
|
+
job_dir = File.join(@tmp_release_dir, "jobs", "#{name}")
|
|
484
|
+
|
|
485
|
+
FileUtils.mkdir_p(job_dir)
|
|
486
|
+
|
|
487
|
+
desc = "job `#{name}/#{version}'"
|
|
488
|
+
result = Bosh::Exec.sh("tar -C #{job_dir} -xzf #{job_tgz} 2>&1", :on_error => :return)
|
|
489
|
+
if result.failed?
|
|
490
|
+
logger.error("Extracting #{desc} archive failed in dir #{job_dir}, " +
|
|
491
|
+
"tar returned #{result.exit_status}, " +
|
|
492
|
+
"output: #{result.output}")
|
|
493
|
+
raise JobInvalidArchive, "Extracting #{desc} archive failed. Check task debug log for details."
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
manifest_file = File.join(job_dir, "job.MF")
|
|
497
|
+
unless File.file?(manifest_file)
|
|
498
|
+
raise JobMissingManifest,
|
|
499
|
+
"Missing job manifest for `#{template.name}'"
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
job_manifest = Psych.load_file(manifest_file)
|
|
503
|
+
|
|
504
|
+
if job_manifest["templates"]
|
|
505
|
+
job_manifest["templates"].each_key do |relative_path|
|
|
506
|
+
path = File.join(job_dir, "templates", relative_path)
|
|
507
|
+
unless File.file?(path)
|
|
508
|
+
raise JobMissingTemplateFile,
|
|
509
|
+
"Missing template file `#{relative_path}' " +
|
|
510
|
+
"for job `#{template.name}'"
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
main_monit_file = File.join(job_dir, "monit")
|
|
516
|
+
aux_monit_files = Dir.glob(File.join(job_dir, "*.monit"))
|
|
517
|
+
|
|
518
|
+
unless File.exists?(main_monit_file) || aux_monit_files.size > 0
|
|
519
|
+
raise JobMissingMonit, "Job `#{template.name}' is missing monit file"
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
template.blobstore_id = BlobUtil.create_blob(job_tgz)
|
|
523
|
+
|
|
524
|
+
package_names = []
|
|
525
|
+
job_manifest["packages"].each do |package_name|
|
|
526
|
+
package = @packages[package_name]
|
|
527
|
+
if package.nil?
|
|
528
|
+
raise JobMissingPackage,
|
|
529
|
+
"Job `#{template.name}' is referencing " +
|
|
530
|
+
"a missing package `#{package_name}'"
|
|
531
|
+
end
|
|
532
|
+
package_names << package.name
|
|
533
|
+
end
|
|
534
|
+
template.package_names = package_names
|
|
535
|
+
|
|
536
|
+
if job_manifest["logs"]
|
|
537
|
+
unless job_manifest["logs"].is_a?(Hash)
|
|
538
|
+
raise JobInvalidLogSpec,
|
|
539
|
+
"Job `#{template.name}' has invalid logs spec format"
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
template.logs = job_manifest["logs"]
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
if job_manifest["properties"]
|
|
546
|
+
unless job_manifest["properties"].is_a?(Hash)
|
|
547
|
+
raise JobInvalidPropertySpec,
|
|
548
|
+
"Job `#{template.name}' has invalid properties spec format"
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
template.properties = job_manifest["properties"]
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
template.save
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
# @param [Array<Array>] jobs Existing jobs metadata
|
|
558
|
+
# @return [void]
|
|
559
|
+
def use_existing_jobs(jobs)
|
|
560
|
+
return if jobs.empty?
|
|
561
|
+
|
|
562
|
+
n_jobs = jobs.size
|
|
563
|
+
event_log.begin_stage("Processing #{n_jobs} existing " +
|
|
564
|
+
"job#{n_jobs > 1 ? "s" : ""}", 1)
|
|
565
|
+
|
|
566
|
+
event_log.track("Verifying checksums") do
|
|
567
|
+
jobs.each do |template, job_meta|
|
|
568
|
+
job_desc = "#{template.name}/#{template.version}"
|
|
569
|
+
|
|
570
|
+
logger.info("Job `#{job_desc}' already exists, " +
|
|
571
|
+
"verifying checksum")
|
|
572
|
+
|
|
573
|
+
expected = template.sha1
|
|
574
|
+
received = job_meta["sha1"]
|
|
575
|
+
|
|
576
|
+
if expected != received
|
|
577
|
+
raise ReleaseExistingJobHashMismatch,
|
|
578
|
+
"`#{job_desc}' checksum mismatch, " +
|
|
579
|
+
"expected #{expected} but received #{received}"
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
logger.info("Job `#{job_desc}' verified")
|
|
583
|
+
register_template(template)
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# Marks job template model as being used by release version
|
|
589
|
+
# @param [Models::Template] template Job template model
|
|
590
|
+
# @return [void]
|
|
591
|
+
def register_template(template)
|
|
592
|
+
@release_version_model.add_template(template)
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
private
|
|
596
|
+
|
|
597
|
+
# Returns the next release version (to be used for rebased release)
|
|
598
|
+
# @return [String]
|
|
599
|
+
def next_release_version
|
|
600
|
+
attrs = {
|
|
601
|
+
:release_id => @release_model.id
|
|
602
|
+
}
|
|
603
|
+
next_version(Models::ReleaseVersion.filter(attrs).all, @version)
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# Returns the next package version (to be used for rebased package)
|
|
607
|
+
# @param [String] name Package name
|
|
608
|
+
# @param [String] version Package version
|
|
609
|
+
# @return [String]
|
|
610
|
+
def next_package_version(name, version)
|
|
611
|
+
attrs = {
|
|
612
|
+
:release_id => @release_model.id,
|
|
613
|
+
:name => name
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
next_version(Models::Package.filter(attrs).all, version)
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
# Returns the next job template version (to be used for rebased template)
|
|
620
|
+
# @param [String] name Template name
|
|
621
|
+
# @param [Fixnum] version Template version
|
|
622
|
+
# @return [String]
|
|
623
|
+
def next_template_version(name, version)
|
|
624
|
+
attrs = {
|
|
625
|
+
:release_id => @release_model.id,
|
|
626
|
+
:name => name
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
next_version(Models::Template.filter(attrs).all, version)
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
# Takes collection of versioned items and returns the version
|
|
633
|
+
# that new item should be promoted to if auto-versioning is used
|
|
634
|
+
# @param [Array<#version>] Collection of items
|
|
635
|
+
# @param [String] version Current version of item
|
|
636
|
+
# @return [String] Next version to be used
|
|
637
|
+
def next_version(collection, version)
|
|
638
|
+
Bosh::Director::NextRebaseVersion.new(collection).calculate(version)
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
# Removes release version model, along with all packages and templates.
|
|
642
|
+
# @return [void]
|
|
643
|
+
def remove_release_version_model
|
|
644
|
+
return unless @release_version_model && !@release_version_model.new?
|
|
645
|
+
|
|
646
|
+
@release_version_model.remove_all_packages
|
|
647
|
+
@release_version_model.remove_all_templates
|
|
648
|
+
@release_version_model.destroy
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
# Picks the best matching package/job from collection based on a simple
|
|
652
|
+
# heuristics: items with same version are preferred, then items
|
|
653
|
+
# with non-dev versions, then items with most compiled packages,
|
|
654
|
+
# then everything else.
|
|
655
|
+
# @param [Array<#name,#version>] collection
|
|
656
|
+
# @param [String] original_version
|
|
657
|
+
def pick_best(collection, original_version)
|
|
658
|
+
collection.sort_by { |item|
|
|
659
|
+
if item.version == original_version
|
|
660
|
+
1
|
|
661
|
+
elsif Bosh::Common::VersionNumber.new(item.version).final?
|
|
662
|
+
2
|
|
663
|
+
elsif item.is_a?(Bosh::Director::Models::Package)
|
|
664
|
+
3000 - [item.compiled_packages.size, 900].min
|
|
665
|
+
else
|
|
666
|
+
3000
|
|
667
|
+
end
|
|
668
|
+
}.first
|
|
669
|
+
end
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
end
|