Class: Athlete::Deployment
- Inherits:
-
Object
- Object
- Athlete::Deployment
show all
- Includes:
- Logging
- Defined in:
- lib/athlete/deployment.rb
Constant Summary
collapse
- @@valid_properties =
%w{
name
marathon_url
build_name
image_name
command
arguments
cpus
memory
environment_variables
instances
minimum_health_capacity
port_mappings
}
- @@locked_properties =
Define properties that cannot be overridden or inherited
%w{
name
marathon_url
build_name
image_name
command
arguments
environment_variables
port_mappings
}
Class Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
Methods included from Logging
#debug, #fatal, #get_loglevel, #info, #loglevel, #warn
Constructor Details
Returns a new instance of Deployment.
39
40
41
42
43
|
# File 'lib/athlete/deployment.rb', line 39
def initialize
@inherit_properties = []
@override_properties = []
setup_dsl_methods
end
|
Class Attribute Details
.deployments ⇒ Object
Returns the value of attribute deployments.
8
9
10
|
# File 'lib/athlete/deployment.rb', line 8
def deployments
@deployments
end
|
Class Method Details
.define(name, &block) ⇒ Object
70
71
72
73
74
75
76
77
78
|
# File 'lib/athlete/deployment.rb', line 70
def self.define(name, &block)
deployment = Athlete::Deployment.new
deployment.name name
deployment.instance_eval(&block)
deployment.fill_default_values
deployment.validate
deployment.connect_to_marathon
@deployments[deployment.name] = deployment
end
|
Instance Method Details
#app_running? ⇒ Boolean
245
246
247
|
# File 'lib/athlete/deployment.rb', line 245
def app_running?
get_running_config != nil
end
|
#connect_to_marathon ⇒ Object
118
119
120
|
# File 'lib/athlete/deployment.rb', line 118
def connect_to_marathon
@marathon_client = Marathon::Client.new(@marathon_url)
end
|
#deploy_or_update ⇒ Object
147
148
149
150
151
152
153
154
155
156
157
|
# File 'lib/athlete/deployment.rb', line 147
def deploy_or_update
if app_running?
debug "App is running in Marathon; performing a warm deploy"
prepare_for_warm_deploy
return @marathon_client.update(@name, marathon_json)
else
debug "App is not running in Marathon; performing a cold deploy"
prepare_for_cold_deploy
return @marathon_client.start(@name, marathon_json)
end
end
|
#deployment_completed? ⇒ Boolean
180
181
182
|
# File 'lib/athlete/deployment.rb', line 180
def deployment_completed?
@marathon_client.find_deployment_by_name(@name) == nil
end
|
#fill_default_values ⇒ Object
80
81
82
83
84
85
|
# File 'lib/athlete/deployment.rb', line 80
def fill_default_values
if !@instances
@instances = 1
@inherit_properties << 'instances'
end
end
|
#get_running_config ⇒ Object
Find the app if it’s already in Marathon (if it’s not there, we get nil)
234
235
236
237
238
239
240
241
242
243
|
# File 'lib/athlete/deployment.rb', line 234
def get_running_config
if @running_config
return @running_config
else
response = @marathon_client.find(@name)
@running_config = response.error? ? nil : response.parsed_response
debug "Retrieved running Marathon configuration: #{@running_config}"
return @running_config
end
end
|
#has_task_failures? ⇒ Boolean
193
194
195
196
197
|
# File 'lib/athlete/deployment.rb', line 193
def has_task_failures?
app_config = @marathon_client.find(@name)
return false if app_config.parsed_response['app']['lastTaskFailure'].nil?
app_config.parsed_response['app']['lastTaskFailure']['version'] == @deploy_response['version']
end
|
#increment_retry ⇒ Object
188
189
190
191
|
# File 'lib/athlete/deployment.rb', line 188
def increment_retry
@retry_count ||= 0
@retry_count = @retry_count + 1
end
|
#linked_build ⇒ Object
229
230
231
|
# File 'lib/athlete/deployment.rb', line 229
def linked_build
@build_name ? Athlete::Build.builds[@build_name] : nil
end
|
#marathon_json ⇒ Object
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
|
# File 'lib/athlete/deployment.rb', line 249
def marathon_json
json = {}
json['id'] = @name
json['cmd'] = @command if @command
json['args'] = @arguments if @arguments
json['cpus'] = @cpus if @cpus
json['mem'] = @memory if @memory
json['env'] = @environment_variables if @environment_variables
json['instances'] = @instances if @instances
if @minimum_health_capacity
json['upgradeStrategy'] = {
'minimumHealthCapacity' => @minimum_health_capacity
}
end
if @port_mappings && !@port_mappings.empty?
json['portMappings'] = @port_mappings
end
if @image_name || @build_name
image = @image_name || linked_build.final_image_name
json['container'] = {
'type' => 'DOCKER',
'docker' => {
'image' => image,
'network' => 'BRIDGE'
}
}
end
debug("Generated Marathon JSON: #{json.to_json}")
json
end
|
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
# File 'lib/athlete/deployment.rb', line 122
def perform
response = deploy_or_update
@deploy_response = response.parsed_response
debug "Entire deployment response: #{response.inspect}"
if response.code == 409
fatal "Deployment did not start; another deployment is in progress"
exit 1
end
info "Polling for deployment state"
state = poll_for_deploy_state
case state
when :retry_exceeded
fatal "App failed to start on Marathon; cancelling deploy"
exit 1
when :complete
info "App is running on Marathon; deployment complete"
else
fatal "App is in unknown state on Marathon"
exit 1
end
end
|
#poll_for_deploy_state ⇒ Object
Poll Marathon to see if the deploy has completed for the given deployed version
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
|
# File 'lib/athlete/deployment.rb', line 161
def poll_for_deploy_state
debug "Entering deploy state polling"
while (not deployment_completed?) && (not retry_exceeded?)
if has_task_failures?
warn "Task failures have occurred during the deploy attempt - this deploy may not succeed"
sleep 1
increment_retry
else
debug "Deploy still in progress with no task failures; sleeping and retrying"
sleep 1
increment_retry
end
end
deployment_completed? ? :complete : :retry_exceeded
end
|
#prepare_for_cold_deploy ⇒ Object
A ‘cold’ deploy is one where the app is not running in Marathon. We have to do additional validation to ensure we can deploy the app, since we don’t have a set of valid parameters in Marathon.
219
220
221
222
223
224
225
226
|
# File 'lib/athlete/deployment.rb', line 219
def prepare_for_cold_deploy
errors = []
errors << "You must specify the parameter 'cpus'" unless @cpus
errors << "You must specify the parameter 'memory'" unless @memory
unless errors.empty?
raise ConfigurationInvalidException, @errors
end
end
|
#prepare_for_warm_deploy ⇒ Object
A ‘warm’ deploy is one where the app is running in Marathon and we’re making changes to it. For each declared configuration property, determine whether it will be always inserted into the remote configuration (:override) or not (:inherit). Think of :override as “Athlete is authoritative for this property”, and :inherit as “Marathon is authoritative for this property”. The way this works in practice is we unset any instance variables that are specified as “inherit”, so that when the Marathon JSON is generated by ‘to_marathon_json` they do not appear in the final deployment JSON.
209
210
211
212
213
214
|
# File 'lib/athlete/deployment.rb', line 209
def prepare_for_warm_deploy
@inherit_properties.each do |property|
debug "Property '#{property}' is specified as :inherit; not supplying to Marathon"
instance_variable_set("@#{property}", nil)
end
end
|
#readable_output ⇒ Object
283
284
285
286
287
288
289
290
291
|
# File 'lib/athlete/deployment.rb', line 283
def readable_output
lines = []
lines << " Deployment name: #{@name}"
@@valid_properties.sort.each do |property|
next if property == 'name'
lines << sprintf(" %-26s: %s", property, instance_variable_get("@#{property}")) if instance_variable_get("@#{property}")
end
puts lines.join("\n")
end
|
#retry_exceeded? ⇒ Boolean
184
185
186
|
# File 'lib/athlete/deployment.rb', line 184
def retry_exceeded?
@retry_count == 10
end
|
#setup_dsl_methods ⇒ Object
45
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/athlete/deployment.rb', line 45
def setup_dsl_methods
@@valid_properties.each do |property|
self.class.class_eval {
define_method(property) do |property_value, override_or_inherit = nil|
instance_variable_set("@#{property}", property_value)
if not @@locked_properties.include?(property)
case override_or_inherit
when :override
@override_properties << property
when :inherit
@inherit_properties << property
else
raise Athlete::ConfigurationInvalidException,
"Property '#{property}' of deployment '#{@name}' specified behaviour as '#{override_or_inherit}', which is not one of :override or :inherit"
end
end
self.class.class_eval{attr_reader property.to_sym}
end
}
end
end
|
#validate ⇒ Object
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
# File 'lib/athlete/deployment.rb', line 87
def validate
errors = []
errors << "You must set one of image_name or build_name" unless @build_name || @image_name
if @build_name && linked_build.nil?
errors << "Build name '#{@build_name}' doesn't match a build in the config file"
end
errors << "You must specify marathon_url" unless @marathon_url
errors << "environment_variables must be a hash" if @environment_variables && !@environment_variables.kind_of?(Hash)
errors << "You must specify only one of command or arguments" if @command && @arguments
error << "The arguments parameter must be specified as an array" if @arguments && !@arguments.kind_of?(Array)
error << "The port_mappings parameter must be an array of hashes" if @port_mappings && !@port_mappings.kind_of?(Array)
unless errors.empty?
raise ConfigurationInvalidException, errors
end
end
|