Skip to content

Commit baf0ad4

Browse files
authored
Deployment updater refactor (#4101)
* moved different update methods into their own classes (scale, canary, etc)
1 parent d7d95b4 commit baf0ad4

File tree

15 files changed

+1539
-969
lines changed

15 files changed

+1539
-969
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
require 'cloud_controller/deployment_updater/actions/scale_down_canceled_processes'
2+
3+
module VCAP::CloudController
4+
module DeploymentUpdater
5+
module Actions
6+
class Canary
7+
attr_reader :deployment, :logger
8+
9+
def initialize(deployment, logger)
10+
@deployment = deployment
11+
@logger = logger
12+
end
13+
14+
def call
15+
deployment.db.transaction do
16+
deployment.lock!
17+
return unless deployment.state == DeploymentModel::PREPAUSED_STATE
18+
return unless all_instances_routable?
19+
20+
ScaleDownCanceledProcesses.new(deployment).call
21+
22+
deployment.update(
23+
last_healthy_at: Time.now,
24+
state: DeploymentModel::PAUSED_STATE,
25+
status_value: DeploymentModel::ACTIVE_STATUS_VALUE,
26+
status_reason: DeploymentModel::PAUSED_STATUS_REASON
27+
)
28+
logger.info("paused-canary-deployment-for-#{deployment.guid}")
29+
end
30+
end
31+
32+
private
33+
34+
def all_instances_routable?
35+
instances = instance_reporters.all_instances_for_app(deployment.deploying_web_process)
36+
instances.all? { |_, val| val[:state] == VCAP::CloudController::Diego::LRP_RUNNING && val[:routable] }
37+
rescue CloudController::Errors::ApiError # the instances_reporter re-raises InstancesUnavailable as ApiError
38+
logger.info("skipping-canary-update-for-#{deployment.guid}")
39+
false
40+
end
41+
42+
def instance_reporters
43+
CloudController::DependencyLocator.instance.instances_reporters
44+
end
45+
end
46+
end
47+
end
48+
end
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
module VCAP::CloudController
2+
module DeploymentUpdater
3+
module Actions
4+
class Cancel
5+
attr_reader :deployment, :logger, :app, :deploying_web_process
6+
7+
def initialize(deployment, logger)
8+
@deployment = deployment
9+
@logger = logger
10+
@app = deployment.app
11+
@deploying_web_process = deployment.deploying_web_process
12+
end
13+
14+
def call
15+
deployment.db.transaction do
16+
app.lock!
17+
return unless deployment.lock!.state == DeploymentModel::CANCELING_STATE
18+
19+
deploying_web_process.lock!
20+
21+
prior_web_process = find_interim_web_process || app.oldest_web_process
22+
prior_web_process.lock!
23+
24+
prior_web_process.update(instances: deployment.original_web_process_instance_count)
25+
26+
app.web_processes.reject { |p| p.guid == prior_web_process.guid }.map(&:destroy)
27+
28+
deployment.update(
29+
state: DeploymentModel::CANCELED_STATE,
30+
status_value: DeploymentModel::FINALIZED_STATUS_VALUE,
31+
status_reason: DeploymentModel::CANCELED_STATUS_REASON
32+
)
33+
end
34+
end
35+
36+
private
37+
38+
def find_interim_web_process
39+
# Find newest interim web process that (a) belongs to a SUPERSEDED (DEPLOYED) deployment and (b) has at least
40+
# one running instance.
41+
app.web_processes_dataset.
42+
qualify.
43+
join(:deployments, deploying_web_process_guid: :guid).
44+
where(deployments__state: DeploymentModel::DEPLOYED_STATE).
45+
where(deployments__status_reason: DeploymentModel::SUPERSEDED_STATUS_REASON).
46+
order(Sequel.desc(:created_at), Sequel.desc(:id)).
47+
find { |p| has_running_instance?(p) }
48+
end
49+
50+
def has_running_instance?(process)
51+
instances = instance_reporters.all_instances_for_app(process)
52+
instances.any? { |_, val| val[:state] == VCAP::CloudController::Diego::LRP_RUNNING }
53+
rescue CloudController::Errors::ApiError # the instances_reporter re-raises InstancesUnavailable as ApiError
54+
false
55+
end
56+
57+
def instance_reporters
58+
CloudController::DependencyLocator.instance.instances_reporters
59+
end
60+
end
61+
end
62+
end
63+
end
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
module VCAP::CloudController
2+
module DeploymentUpdater
3+
module Actions
4+
class Finalize
5+
attr_reader :deployment, :deploying_web_process, :app
6+
7+
def initialize(deployment)
8+
@deployment = deployment
9+
@app = deployment.app
10+
@deploying_web_process = deployment.deploying_web_process
11+
end
12+
13+
def call
14+
app.web_processes.reject { |p| p.guid == deploying_web_process.guid }.map(&:destroy)
15+
16+
update_non_web_processes
17+
restart_non_web_processes
18+
deployment.update(
19+
state: DeploymentModel::DEPLOYED_STATE,
20+
status_value: DeploymentModel::FINALIZED_STATUS_VALUE,
21+
status_reason: DeploymentModel::DEPLOYED_STATUS_REASON
22+
)
23+
end
24+
25+
private
26+
27+
def update_non_web_processes
28+
return if deploying_web_process.revision.nil?
29+
30+
app.processes.reject(&:web?).each do |process|
31+
process.update(command: deploying_web_process.revision.commands_by_process_type[process.type])
32+
end
33+
end
34+
35+
def restart_non_web_processes
36+
app.processes.reject(&:web?).each do |process|
37+
VCAP::CloudController::ProcessRestart.restart(
38+
process: process,
39+
config: Config.config,
40+
stop_in_runtime: true,
41+
revision: deploying_web_process.revision
42+
)
43+
end
44+
end
45+
end
46+
end
47+
end
48+
end
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
require 'cloud_controller/deployment_updater/actions/scale_down_canceled_processes'
2+
require 'cloud_controller/deployment_updater/actions/scale_down_old_process'
3+
require 'cloud_controller/deployment_updater/actions/finalize'
4+
5+
module VCAP::CloudController
6+
module DeploymentUpdater
7+
module Actions
8+
class Scale
9+
attr_reader :deployment, :logger, :app
10+
11+
def initialize(deployment, logger)
12+
@deployment = deployment
13+
@logger = logger
14+
@app = deployment.app
15+
end
16+
17+
def call
18+
deployment.db.transaction do
19+
return unless deployment.lock!.state == DeploymentModel::DEPLOYING_STATE
20+
return unless all_instances_routable?
21+
22+
app.lock!
23+
oldest_web_process_with_instances.lock!
24+
deploying_web_process.lock!
25+
26+
deployment.update(
27+
last_healthy_at: Time.now,
28+
state: DeploymentModel::DEPLOYING_STATE,
29+
status_value: DeploymentModel::ACTIVE_STATUS_VALUE,
30+
status_reason: DeploymentModel::DEPLOYING_STATUS_REASON
31+
)
32+
33+
if deploying_web_process.instances >= deployment.original_web_process_instance_count
34+
Finalize.new(deployment).call
35+
return
36+
end
37+
38+
ScaleDownCanceledProcesses.new(deployment).call
39+
ScaleDownOldProcess.new(deployment, oldest_web_process_with_instances, desired_old_instances).call
40+
41+
deploying_web_process.update(instances: desired_new_instances)
42+
end
43+
end
44+
45+
private
46+
47+
def desired_old_instances
48+
[(oldest_web_process_with_instances.instances - deployment.max_in_flight), 0].max
49+
end
50+
51+
def desired_new_instances
52+
[deploying_web_process.instances + deployment.max_in_flight, deployment.original_web_process_instance_count].min
53+
end
54+
55+
def oldest_web_process_with_instances
56+
@oldest_web_process_with_instances ||= app.web_processes.select { |process| process.instances > 0 }.min_by { |p| [p.created_at, p.id] }
57+
end
58+
59+
def deploying_web_process
60+
@deploying_web_process ||= deployment.deploying_web_process
61+
end
62+
63+
def all_instances_routable?
64+
instances = instance_reporters.all_instances_for_app(deployment.deploying_web_process)
65+
instances.all? { |_, val| val[:state] == VCAP::CloudController::Diego::LRP_RUNNING && val[:routable] }
66+
rescue CloudController::Errors::ApiError # the instances_reporter re-raises InstancesUnavailable as ApiError
67+
logger.info("skipping-deployment-update-for-#{deployment.guid}")
68+
false
69+
end
70+
71+
def instance_reporters
72+
CloudController::DependencyLocator.instance.instances_reporters
73+
end
74+
end
75+
end
76+
end
77+
end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module VCAP::CloudController
2+
module DeploymentUpdater
3+
module Actions
4+
class ScaleDownCanceledProcesses
5+
attr_reader :deployment
6+
7+
def initialize(deployment)
8+
@deployment = deployment
9+
end
10+
11+
def call
12+
superseded_processes.each { |p| p.lock!.update(instances: 0) }
13+
end
14+
15+
private
16+
17+
def superseded_processes
18+
# Find interim web processes that (a) belong to a SUPERSEDED (CANCELED) deployment and (b) have instances
19+
# and scale them to zero.
20+
deployment.app.web_processes_dataset.
21+
qualify.
22+
join(:deployments, deploying_web_process_guid: :guid).
23+
where(deployments__state: DeploymentModel::CANCELED_STATE).
24+
where(deployments__status_reason: DeploymentModel::SUPERSEDED_STATUS_REASON).
25+
where(Sequel[:processes__instances] > 0)
26+
end
27+
end
28+
end
29+
end
30+
end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
module VCAP::CloudController
2+
module DeploymentUpdater
3+
module Actions
4+
class ScaleDownOldProcess
5+
attr_reader :deployment, :process, :app, :desired_instances
6+
7+
def initialize(deployment, process, desired_instances)
8+
@deployment = deployment
9+
@app = deployment.app
10+
@process = process
11+
@desired_instances = desired_instances
12+
end
13+
14+
def call
15+
if desired_instances == 0 && is_interim_process?(process)
16+
process.destroy
17+
return
18+
end
19+
20+
process.update(instances: desired_instances)
21+
end
22+
23+
private
24+
25+
def is_original_web_process?(process)
26+
process == app.oldest_web_process
27+
end
28+
29+
def is_interim_process?(process)
30+
!is_original_web_process?(process)
31+
end
32+
end
33+
end
34+
end
35+
end

0 commit comments

Comments
 (0)