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
volumes
}
- @@locked_properties =
Define properties that cannot be overridden or inherited
%w{
name
marathon_url
build_name
image_name
command
arguments
environment_variables
port_mappings
volumes
}
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.
41
42
43
44
45
|
# File 'lib/athlete/deployment.rb', line 41
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
72
73
74
75
76
77
78
79
80
|
# File 'lib/athlete/deployment.rb', line 72
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
247
248
249
|
# File 'lib/athlete/deployment.rb', line 247
def app_running?
get_running_config != nil
end
|
#connect_to_marathon ⇒ Object
120
121
122
|
# File 'lib/athlete/deployment.rb', line 120
def connect_to_marathon
@marathon_client = Marathon::Client.new(@marathon_url)
end
|
#deploy_or_update ⇒ Object
149
150
151
152
153
154
155
156
157
158
159
|
# File 'lib/athlete/deployment.rb', line 149
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
182
183
184
|
# File 'lib/athlete/deployment.rb', line 182
def deployment_completed?
@marathon_client.find_deployment_by_name(@name) == nil
end
|
#fill_default_values ⇒ Object
82
83
84
85
86
87
|
# File 'lib/athlete/deployment.rb', line 82
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)
236
237
238
239
240
241
242
243
244
245
|
# File 'lib/athlete/deployment.rb', line 236
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
195
196
197
198
199
|
# File 'lib/athlete/deployment.rb', line 195
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
190
191
192
193
|
# File 'lib/athlete/deployment.rb', line 190
def increment_retry
@retry_count ||= 0
@retry_count = @retry_count + 1
end
|
#linked_build ⇒ Object
231
232
233
|
# File 'lib/athlete/deployment.rb', line 231
def linked_build
@build_name ? Athlete::Build.builds[@build_name] : nil
end
|
#marathon_json ⇒ Object
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
282
283
284
285
286
287
288
289
|
# File 'lib/athlete/deployment.rb', line 251
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 @image_name || @build_name
image = @image_name || linked_build.final_image_name
json['container'] = {
'type' => 'DOCKER',
'docker' => {
'image' => image,
'network' => 'BRIDGE'
}
}
if @port_mappings && !@port_mappings.empty?
json['container']['docker']['portMappings'] = @port_mappings
end
if @volumes
json['container']['volumes'] = @volumes
end
end
debug("Generated Marathon JSON: #{json.to_json}")
json
end
|
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
# File 'lib/athlete/deployment.rb', line 124
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
# File 'lib/athlete/deployment.rb', line 163
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.
221
222
223
224
225
226
227
228
|
# File 'lib/athlete/deployment.rb', line 221
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.
211
212
213
214
215
216
|
# File 'lib/athlete/deployment.rb', line 211
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
291
292
293
294
295
296
297
298
299
|
# File 'lib/athlete/deployment.rb', line 291
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
186
187
188
|
# File 'lib/athlete/deployment.rb', line 186
def retry_exceeded?
@retry_count == 10
end
|
#setup_dsl_methods ⇒ Object
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
# File 'lib/athlete/deployment.rb', line 47
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
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
117
118
|
# File 'lib/athlete/deployment.rb', line 89
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
|