Skip to content
This repository was archived by the owner on Jun 2, 2021. It is now read-only.

Commit 0b291cc

Browse files
Derik EvangelistaFelisiaMblgm
committed
v3: Create managed service instance async
[#171649250](https://www.pivotaltracker.com/story/show/171649250) Co-authored-by: Felisia Martini <fmartini@pivotal.io> Co-authored-by: Derik Evangelista <devangelista@pivotal.io> Co-authored-by: George Blue <gblue@pivotal.io>
1 parent d1cf638 commit 0b291cc

9 files changed

Lines changed: 1144 additions & 109 deletions

File tree

app/jobs/pollable_job_wrapper.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ def after_enqueue(job)
1515
end
1616

1717
def success(job)
18-
change_state(job, PollableJobModel::COMPLETE_STATE)
18+
if @handler.respond_to?(:success)
19+
@handler.success(job)
20+
else
21+
change_state(job, PollableJobModel::COMPLETE_STATE)
22+
end
1923
end
2024

2125
def error(job, exception)

app/jobs/v3/services/create_service_instance_job.rb

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require 'jobs/v3/services/service_broker_catalog_updater'
1+
require 'jobs/v3/services/fetch_last_operation_job'
22

33
module VCAP::CloudController
44
module V3
@@ -9,28 +9,47 @@ def initialize(service_instance_guid, arbitrary_parameters: {})
99
end
1010

1111
def perform
12-
service_instance = ManagedServiceInstance.last(guid: service_instance_guid)
1312
client = VCAP::Services::ServiceClientProvider.provide({ instance: service_instance })
1413

1514
begin
1615
broker_response = client.provision(
1716
service_instance,
18-
accepts_incomplete: false,
19-
arbitrary_parameters: arbitrary_parameters,
20-
maintenance_info: service_instance.service_plan.maintenance_info
17+
accepts_incomplete: true,
18+
arbitrary_parameters: arbitrary_parameters,
19+
maintenance_info: service_instance.service_plan.maintenance_info
2120
)
2221
rescue => e
2322
service_instance.save_with_new_operation({}, {
24-
type: 'create',
25-
state: 'failed',
26-
description: e.message,
23+
type: 'create',
24+
state: 'failed',
25+
description: e.message,
2726
})
2827
raise e
2928
end
3029

3130
service_instance.save_with_new_operation(broker_response[:instance], broker_response[:last_operation])
3231
end
3332

33+
def success(job)
34+
pollable_job = PollableJobModel.first(delayed_job_guid: job.guid)
35+
if service_instance.operation_in_progress?
36+
polling_job = VCAP::CloudController::V3::FetchLastOperationJob.new(
37+
service_instance_guid: service_instance.guid,
38+
pollable_job_guid: pollable_job.guid,
39+
request_attrs: @arbitrary_parameters
40+
)
41+
enqueuer = Jobs::Enqueuer.new(polling_job, queue: Jobs::Queues.generic)
42+
delayed_job = enqueuer.enqueue
43+
44+
pollable_job.update(
45+
state: PollableJobModel::POLLING_STATE,
46+
delayed_job_guid: delayed_job.guid
47+
)
48+
else
49+
pollable_job.update(state: PollableJobModel::COMPLETE_STATE)
50+
end
51+
end
52+
3453
def job_name_in_configuration
3554
:service_instance_create
3655
end
@@ -53,6 +72,10 @@ def display_name
5372

5473
private
5574

75+
def service_instance
76+
@service_instance ||= ManagedServiceInstance.first(guid: service_instance_guid)
77+
end
78+
5679
attr_reader :service_instance_guid, :arbitrary_parameters
5780
end
5881
end
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
require 'jobs/v2/services/asynchronous_operations'
2+
require 'jobs/cc_job'
3+
4+
module VCAP::CloudController
5+
module V3
6+
class FetchLastOperationJob < VCAP::CloudController::Jobs::CCJob
7+
include VCAP::CloudController::Jobs::Services::AsynchronousOperations
8+
9+
attr_accessor :service_instance_guid, :request_attrs, :poll_interval, :end_timestamp
10+
11+
def initialize(service_instance_guid:, request_attrs:, end_timestamp: nil, pollable_job_guid:)
12+
@service_instance_guid = service_instance_guid
13+
@request_attrs = request_attrs
14+
@end_timestamp = end_timestamp || new_end_timestamp
15+
@pollable_job_guid = pollable_job_guid
16+
update_polling_interval
17+
end
18+
19+
def success(_)
20+
service_instance = ManagedServiceInstance.first(guid: service_instance_guid)
21+
22+
if service_instance.terminal_state?
23+
pollable_job.update(state: PollableJobModel::COMPLETE_STATE)
24+
else
25+
try_again
26+
end
27+
end
28+
29+
def error(job, exception)
30+
wrapper = VCAP::CloudController::Jobs::PollableJobWrapper.new(job)
31+
wrapper.failure(job)
32+
wrapper.error(job, exception)
33+
end
34+
35+
def perform
36+
service_instance = ManagedServiceInstance.first(guid: service_instance_guid)
37+
gone! if service_instance.nil?
38+
39+
intended_operation = service_instance.last_operation
40+
aborted! if intended_operation.type != 'create'
41+
42+
client = VCAP::Services::ServiceClientProvider.provide(instance: service_instance)
43+
44+
last_operation_result = client.fetch_service_instance_last_operation(service_instance)
45+
update_with_attributes(last_operation_result[:last_operation], service_instance, intended_operation)
46+
47+
@retry_after = last_operation_result[:retry_after]
48+
rescue HttpRequestError, HttpResponseError, Sequel::Error => e
49+
logger = Steno.logger('cc-background')
50+
logger.error("There was an error while fetching the service instance operation state: #{e}")
51+
@retry_after = nil
52+
try_again
53+
end
54+
55+
def max_attempts
56+
1
57+
end
58+
59+
private
60+
61+
def gone!
62+
raise CloudController::Errors::ApiError.new_from_details('ServiceInstanceNotFound', @service_instance_guid)
63+
end
64+
65+
def aborted!
66+
raise CloudController::Errors::ApiError.new_from_details('UnableToPerform', 'Create', 'delete in progress')
67+
end
68+
69+
def pollable_job
70+
PollableJobModel.where(guid: @pollable_job_guid)
71+
end
72+
73+
def try_again
74+
delayed_job = retry_job(retry_after_header: @retry_interval)
75+
pollable_job.update(
76+
state: PollableJobModel::POLLING_STATE,
77+
delayed_job_guid: delayed_job.guid
78+
)
79+
end
80+
81+
def update_with_attributes(last_operation, service_instance, intended_operation)
82+
ServiceInstance.db.transaction do
83+
service_instance.lock!
84+
return unless intended_operation == service_instance.last_operation
85+
86+
service_instance.save_and_update_operation(
87+
last_operation: last_operation.slice(:state, :description)
88+
)
89+
90+
if service_instance.last_operation.state == 'succeeded'
91+
apply_proposed_changes(service_instance)
92+
end
93+
end
94+
end
95+
96+
def end_timestamp_reached
97+
ManagedServiceInstance.first(guid: service_instance_guid).save_and_update_operation(
98+
last_operation: {
99+
state: 'failed',
100+
description: 'Service Broker failed to provision within the required time.',
101+
}
102+
)
103+
end
104+
105+
def apply_proposed_changes(service_instance)
106+
if service_instance.last_operation.type == 'delete'
107+
service_instance.last_operation.destroy
108+
service_instance.destroy
109+
else
110+
service_instance.save_and_update_operation(service_instance.last_operation.proposed_changes)
111+
end
112+
end
113+
114+
def service_plan
115+
ManagedServiceInstance.first(guid: service_instance_guid).try(:service_plan)
116+
rescue Sequel::Error => e
117+
Steno.logger('cc-background').error("There was an error while fetching the service instance: #{e}")
118+
nil
119+
end
120+
end
121+
122+
class ServiceInstanceGoneError < StandardError
123+
end
124+
end
125+
end

app/models/runtime/pollable_job_model.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ class PollableJobModel < Sequel::Model(:jobs)
33
PROCESSING_STATE = 'PROCESSING'.freeze
44
COMPLETE_STATE = 'COMPLETE'.freeze
55
FAILED_STATE = 'FAILED'.freeze
6+
POLLING_STATE = 'POLLING'.freeze
67

78
one_to_many :warnings, class: 'VCAP::CloudController::JobWarningModel', key: :job_id
89

docs/v3/source/includes/resources/jobs/_object.md.erb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Name | Type | Description
1313
**created_at** | _datetime_ | The time with zone when the object was created.
1414
**updated_at** | _datetime_ | The time with zone when the object was last updated.
1515
**operation** | _string_ | Current desired operation of the job on a model.
16-
**state** | _string_ | State of the job. Valid values are `PROCESSING`, `COMPLETE`, or` FAILED`.
16+
**state** | _string_ | State of the job. Valid values are `PROCESSING`, `POLLING`, `COMPLETE`, or` FAILED`.
1717
**links** | [_links object_](#links) | Links to related resources.
1818
**errors** | [_errors list_](#errors) | Array of errors that occurred while processing the job.
19+
20+
Note: `POLLING` happens during asynchronous services operations that require polling the last operation from the service broker.

0 commit comments

Comments
 (0)