wakame 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. data/History.txt +20 -0
  2. data/README.rdoc +63 -0
  3. data/Rakefile +86 -0
  4. data/VERSION +1 -0
  5. data/app_generators/wakame/templates/README +0 -0
  6. data/app_generators/wakame/templates/Rakefile +18 -0
  7. data/app_generators/wakame/templates/bin/wakame-agent +9 -0
  8. data/app_generators/wakame/templates/bin/wakame-master +9 -0
  9. data/app_generators/wakame/templates/bin/wakameadm +9 -0
  10. data/app_generators/wakame/templates/cluster/resources/apache_app/apache_app.rb +54 -0
  11. data/app_generators/wakame/templates/cluster/resources/apache_app/conf/apache2.conf +46 -0
  12. data/app_generators/wakame/templates/cluster/resources/apache_app/conf/envvars-app +7 -0
  13. data/app_generators/wakame/templates/cluster/resources/apache_app/conf/sites-app.conf +23 -0
  14. data/app_generators/wakame/templates/cluster/resources/apache_app/conf/system-app.conf +67 -0
  15. data/app_generators/wakame/templates/cluster/resources/apache_app/init.d/apache2-app +192 -0
  16. data/app_generators/wakame/templates/cluster/resources/apache_lb/apache_lb.rb +56 -0
  17. data/app_generators/wakame/templates/cluster/resources/apache_lb/conf/apache2.conf +46 -0
  18. data/app_generators/wakame/templates/cluster/resources/apache_lb/conf/envvars-lb +6 -0
  19. data/app_generators/wakame/templates/cluster/resources/apache_lb/conf/sites-lb.conf +54 -0
  20. data/app_generators/wakame/templates/cluster/resources/apache_lb/conf/system-lb.conf +75 -0
  21. data/app_generators/wakame/templates/cluster/resources/apache_lb/init.d/apache2-lb +192 -0
  22. data/app_generators/wakame/templates/cluster/resources/apache_www/apache_www.rb +50 -0
  23. data/app_generators/wakame/templates/cluster/resources/apache_www/conf/apache2.conf +47 -0
  24. data/app_generators/wakame/templates/cluster/resources/apache_www/conf/envvars-www +7 -0
  25. data/app_generators/wakame/templates/cluster/resources/apache_www/conf/sites-www.conf +23 -0
  26. data/app_generators/wakame/templates/cluster/resources/apache_www/conf/system-www.conf +63 -0
  27. data/app_generators/wakame/templates/cluster/resources/apache_www/init.d/apache2-www +192 -0
  28. data/app_generators/wakame/templates/cluster/resources/ec2_elastic_ip/ec2_elastic_ip.rb +39 -0
  29. data/app_generators/wakame/templates/cluster/resources/mysql_master/conf/my.cnf +154 -0
  30. data/app_generators/wakame/templates/cluster/resources/mysql_master/init.d/mysql +185 -0
  31. data/app_generators/wakame/templates/cluster/resources/mysql_master/mysql_master.rb +174 -0
  32. data/app_generators/wakame/templates/config/boot.rb +85 -0
  33. data/app_generators/wakame/templates/config/cluster.rb +64 -0
  34. data/app_generators/wakame/templates/config/environments/common.rb +0 -0
  35. data/app_generators/wakame/templates/config/environments/ec2.rb +3 -0
  36. data/app_generators/wakame/templates/config/environments/stand_alone.rb +0 -0
  37. data/app_generators/wakame/templates/config/init.d/wakame-agent +72 -0
  38. data/app_generators/wakame/templates/config/init.d/wakame-master +73 -0
  39. data/app_generators/wakame/wakame_generator.rb +124 -0
  40. data/bin/wakame +18 -0
  41. data/contrib/imagesetup.sh +77 -0
  42. data/lib/ext/eventmachine.rb +86 -0
  43. data/lib/ext/shellwords.rb +172 -0
  44. data/lib/ext/uri.rb +15 -0
  45. data/lib/wakame/action.rb +156 -0
  46. data/lib/wakame/actions/destroy_instances.rb +39 -0
  47. data/lib/wakame/actions/launch_cluster.rb +31 -0
  48. data/lib/wakame/actions/migrate_service.rb +65 -0
  49. data/lib/wakame/actions/propagate_instances.rb +95 -0
  50. data/lib/wakame/actions/reload_service.rb +21 -0
  51. data/lib/wakame/actions/scaleout_when_high_load.rb +44 -0
  52. data/lib/wakame/actions/shutdown_cluster.rb +22 -0
  53. data/lib/wakame/actions/shutdown_vm.rb +19 -0
  54. data/lib/wakame/actions/start_service.rb +64 -0
  55. data/lib/wakame/actions/stop_service.rb +49 -0
  56. data/lib/wakame/actions/util.rb +71 -0
  57. data/lib/wakame/actor/daemon.rb +37 -0
  58. data/lib/wakame/actor/service_monitor.rb +21 -0
  59. data/lib/wakame/actor/system.rb +46 -0
  60. data/lib/wakame/actor.rb +33 -0
  61. data/lib/wakame/agent.rb +226 -0
  62. data/lib/wakame/amqp_client.rb +219 -0
  63. data/lib/wakame/command/action_status.rb +62 -0
  64. data/lib/wakame/command/actor.rb +23 -0
  65. data/lib/wakame/command/clone_service.rb +12 -0
  66. data/lib/wakame/command/launch_cluster.rb +15 -0
  67. data/lib/wakame/command/migrate_service.rb +21 -0
  68. data/lib/wakame/command/propagate_service.rb +24 -0
  69. data/lib/wakame/command/shutdown_cluster.rb +15 -0
  70. data/lib/wakame/command/status.rb +81 -0
  71. data/lib/wakame/command.rb +31 -0
  72. data/lib/wakame/command_queue.rb +44 -0
  73. data/lib/wakame/configuration.rb +93 -0
  74. data/lib/wakame/daemonize.rb +96 -0
  75. data/lib/wakame/event.rb +232 -0
  76. data/lib/wakame/event_dispatcher.rb +154 -0
  77. data/lib/wakame/graph.rb +79 -0
  78. data/lib/wakame/initializer.rb +162 -0
  79. data/lib/wakame/instance_counter.rb +78 -0
  80. data/lib/wakame/logger.rb +12 -0
  81. data/lib/wakame/manager/commands.rb +134 -0
  82. data/lib/wakame/master.rb +369 -0
  83. data/lib/wakame/monitor/agent.rb +50 -0
  84. data/lib/wakame/monitor/service.rb +183 -0
  85. data/lib/wakame/monitor.rb +69 -0
  86. data/lib/wakame/packets.rb +160 -0
  87. data/lib/wakame/queue_declare.rb +14 -0
  88. data/lib/wakame/rule.rb +116 -0
  89. data/lib/wakame/rule_engine.rb +202 -0
  90. data/lib/wakame/runner/administrator_command.rb +112 -0
  91. data/lib/wakame/runner/agent.rb +81 -0
  92. data/lib/wakame/runner/master.rb +93 -0
  93. data/lib/wakame/scheduler.rb +251 -0
  94. data/lib/wakame/service.rb +914 -0
  95. data/lib/wakame/template.rb +189 -0
  96. data/lib/wakame/trigger.rb +66 -0
  97. data/lib/wakame/triggers/instance_count_update.rb +45 -0
  98. data/lib/wakame/triggers/load_history.rb +107 -0
  99. data/lib/wakame/triggers/maintain_ssh_known_hosts.rb +43 -0
  100. data/lib/wakame/triggers/process_command.rb +34 -0
  101. data/lib/wakame/triggers/shutdown_unused_vm.rb +16 -0
  102. data/lib/wakame/util.rb +569 -0
  103. data/lib/wakame/vm_manipulator.rb +186 -0
  104. data/lib/wakame.rb +59 -0
  105. data/tasks/ec2.rake +127 -0
  106. data/tests/cluster.json +3 -0
  107. data/tests/conf/a +1 -0
  108. data/tests/conf/b +1 -0
  109. data/tests/conf/c +1 -0
  110. data/tests/setup_agent.rb +39 -0
  111. data/tests/setup_master.rb +28 -0
  112. data/tests/test_actor.rb +54 -0
  113. data/tests/test_agent.rb +218 -0
  114. data/tests/test_amqp_client.rb +94 -0
  115. data/tests/test_graph.rb +36 -0
  116. data/tests/test_master.rb +167 -0
  117. data/tests/test_monitor.rb +47 -0
  118. data/tests/test_rule_engine.rb +127 -0
  119. data/tests/test_scheduler.rb +123 -0
  120. data/tests/test_service.rb +60 -0
  121. data/tests/test_template.rb +67 -0
  122. data/tests/test_uri_amqp.rb +19 -0
  123. data/tests/test_util.rb +71 -0
  124. data/wakame_generators/resource/resource_generator.rb +54 -0
  125. data/wakame_generators/resource/templates/apache_app/apache_app.rb +60 -0
  126. data/wakame_generators/resource/templates/apache_app/conf/apache2.conf +46 -0
  127. data/wakame_generators/resource/templates/apache_app/conf/envvars-app +7 -0
  128. data/wakame_generators/resource/templates/apache_app/conf/sites-app.conf +23 -0
  129. data/wakame_generators/resource/templates/apache_app/conf/system-app.conf +67 -0
  130. data/wakame_generators/resource/templates/apache_app/init.d/apache2-app +192 -0
  131. data/wakame_generators/resource/templates/apache_lb/apache_lb.rb +67 -0
  132. data/wakame_generators/resource/templates/apache_lb/conf/apache2.conf +46 -0
  133. data/wakame_generators/resource/templates/apache_lb/conf/envvars-lb +6 -0
  134. data/wakame_generators/resource/templates/apache_lb/conf/sites-lb.conf +54 -0
  135. data/wakame_generators/resource/templates/apache_lb/conf/system-lb.conf +75 -0
  136. data/wakame_generators/resource/templates/apache_lb/init.d/apache2-lb +192 -0
  137. data/wakame_generators/resource/templates/apache_www/apache_www.rb +56 -0
  138. data/wakame_generators/resource/templates/apache_www/conf/apache2.conf +47 -0
  139. data/wakame_generators/resource/templates/apache_www/conf/envvars-www +7 -0
  140. data/wakame_generators/resource/templates/apache_www/conf/sites-www.conf +23 -0
  141. data/wakame_generators/resource/templates/apache_www/conf/system-www.conf +63 -0
  142. data/wakame_generators/resource/templates/apache_www/init.d/apache2-www +192 -0
  143. data/wakame_generators/resource/templates/ec2_elastic_ip/ec2_elastic_ip.rb +39 -0
  144. data/wakame_generators/resource/templates/mysql_master/conf/my.cnf +154 -0
  145. data/wakame_generators/resource/templates/mysql_master/init.d/mysql +185 -0
  146. data/wakame_generators/resource/templates/mysql_master/mysql_master.rb +119 -0
  147. metadata +289 -0
@@ -0,0 +1,914 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'ostruct'
4
+
5
+ require 'wakame'
6
+ require 'wakame/util'
7
+
8
+ module Wakame
9
+ module Service
10
+ class ServiceError < StandardError; end
11
+ class ServiceOk < StandardError; end
12
+ class ServicePropagationError < ServiceError; end
13
+
14
+
15
+ STATUS_OFFLINE = 0
16
+ STATUS_ONLINE = 1
17
+ STATUS_UNKNOWN = 2
18
+ STATUS_FAIL = 3
19
+ STATUS_STARTING = 4
20
+ STATUS_STOPPING = 5
21
+ STATUS_RELOADING = 6
22
+ STATUS_MIGRATING = 7
23
+
24
+ class Agent
25
+ include ThreadImmutable
26
+ include AttributeHelper
27
+ STATUS_OFFLINE = 0
28
+ STATUS_ONLINE = 1
29
+ STATUS_UNKNOWN = 2
30
+ STATUS_TIMEOUT = 3
31
+
32
+ attr_accessor :agent_id, :uptime, :last_ping_at, :attr, :services, :root_path
33
+ thread_immutable_methods :agent_id=, :uptime=, :last_ping_at=, :attr=, :services=, :root_path=
34
+
35
+ def initialize(agent_id=nil)
36
+ bind_thread
37
+ @services = {}
38
+ @agent_id = agent_id
39
+ @last_ping_at = Time.now
40
+ @status = STATUS_ONLINE
41
+ end
42
+
43
+ def agent_ip
44
+ attr[:local_ipv4]
45
+ end
46
+
47
+ def [](key)
48
+ attr[key]
49
+ end
50
+
51
+ attr_reader :status
52
+
53
+ def status=(status)
54
+ if @status != status
55
+ @status = status
56
+ ED.fire_event(Event::AgentStatusChanged.new(self))
57
+ # Send status specific event
58
+ case status
59
+ when STATUS_TIMEOUT
60
+ ED.fire_event(Event::AgentTimedOut.new(self))
61
+ end
62
+ end
63
+ @status
64
+ end
65
+ thread_immutable_methods :status=
66
+
67
+ def has_service_type?(key)
68
+ svc_class = case key
69
+ when Service::ServiceInstance
70
+ key.resource.class
71
+ when Service::Resource
72
+ key.class
73
+ when Class
74
+ key
75
+ else
76
+ raise ArgumentError
77
+ end
78
+
79
+ services.any? { |k, v|
80
+ Wakame.log.debug( "#{agent_id} of service #{v.resource.class}. v.resource.class == svc_class result to #{v.resource.class == svc_class}")
81
+
82
+ v.property.class == svc_class
83
+ }
84
+ end
85
+
86
+
87
+ def dump_status
88
+ {:agent_id => @agent_id, :status => @status, :last_ping_at => @last_ping_at, :attr => attr.dup,
89
+ :services => services.keys.dup
90
+ }
91
+ end
92
+
93
+ end
94
+
95
+
96
+ class ServiceCluster
97
+ include ThreadImmutable
98
+
99
+ attr_reader :dg, :instance_id, :status_changed_at, :rule_engine, :master
100
+ attr_reader :status
101
+
102
+ STATUS_OFFLINE = 0
103
+ STATUS_ONLINE = 1
104
+ STATUS_PARTIAL_ONLINE = 2
105
+
106
+ def initialize(master, &blk)
107
+ bind_thread
108
+ @master = master
109
+ @instance_id =Wakame.gen_id
110
+ @rule_engine ||= RuleEngine.new(self)
111
+ prepare
112
+
113
+ instance_eval(&blk) if blk
114
+ end
115
+
116
+ def define_rule(&blk)
117
+ @rule_engine ||= RuleEngine.new(self)
118
+
119
+ blk.call(@rule_engine)
120
+ end
121
+
122
+ def add_resource(resource, name=nil)
123
+ #if name.nil? || @name2prop.has_key? name
124
+ # name = "#{resource.class.to_s}#{name2prop.size + 1}"
125
+ #end
126
+ raise ArgumentError unless resource.is_a? Resource
127
+ raise "Duplicate resource type registration" if @properties.has_key? resource.class.to_s
128
+ @properties[resource.class.to_s]=resource
129
+ @dg.add_object(resource.class.to_s)
130
+
131
+ #name
132
+ end
133
+ thread_immutable_methods :add_resource
134
+
135
+ def set_dependency(prop_name1, prop_name2)
136
+ prop1 = @properties[prop_name1.to_s]
137
+ prop2 = @properties[prop_name2.to_s]
138
+ return unless prop1.is_a?(Property) && prop2.is_a?(Property) && prop1 != prop2
139
+ @dg.set_dependency(prop_name1.to_s, prop_name2.to_s)
140
+ end
141
+ thread_immutable_methods :set_dependency
142
+
143
+ def included_instance?(service_instance_id)
144
+ @services.has_key? service_instance_id
145
+ end
146
+
147
+
148
+ def shutdown
149
+ end
150
+ thread_immutable_methods :shutdown
151
+
152
+ # Create service instance objects which will be equivalent with the number min_instance.
153
+ # The agents are not assigned at this point.
154
+ def launch
155
+ @properties.each { |n, p|
156
+ count = instance_count(p)
157
+ if p.min_instances > count
158
+ (p.min_instances - count).times {
159
+ propagate(p.class)
160
+ }
161
+ end
162
+ }
163
+ end
164
+ thread_immutable_methods :launch
165
+
166
+ def destroy(service_instance_id)
167
+ raise("Unknown service instance : #{service_instance_id}") unless included_instance?(service_instance_id)
168
+ svc_inst = @services[service_instance_id]
169
+ old_agent = svc_inst.unbind_agent
170
+ svc_inst.unbind_cluster
171
+ @services.delete(service_instance_id)
172
+ if old_agent
173
+ Wakame.log.debug("#{svc_inst.property.class}(#{svc_inst.instance_id}) has been destroied from Agent #{old_agent.agent_id}")
174
+ else
175
+ Wakame.log.debug("#{svc_inst.property.class}(#{svc_inst.instance_id}) has been destroied.")
176
+ end
177
+ end
178
+ thread_immutable_methods :destroy
179
+
180
+ def propagate(property_name, force=false)
181
+ property_name = case property_name
182
+ when Class, String
183
+ property_name.to_s
184
+ when Property
185
+ property_name.class.to_s
186
+ else
187
+ raise ArgumentError
188
+ end
189
+ prop = @properties[property_name.to_s] || raise("Unknown property name: #{property_name.to_s}")
190
+ if force == false
191
+ instnum = instance_count(property_name)
192
+ if instnum >= prop.max_instances
193
+ Wakame.log.info("#{prop.class} has been reached max_instance limit: max=#{prop.max_instance}")
194
+ raise ServicePropagationError, "#{prop.class} has been reached to max_instance limit"
195
+ end
196
+ end
197
+
198
+ svc_inst = Service::ServiceInstance.new(prop)
199
+ svc_inst.bind_cluster(self)
200
+ #svc_inst.bind_agent(agent) if agent
201
+
202
+ @services[svc_inst.instance_id]=svc_inst
203
+ svc_inst
204
+ end
205
+ thread_immutable_methods :propagate
206
+
207
+ def instance_count(property_name=nil)
208
+ return @services.size if property_name.nil?
209
+
210
+ property_name = case property_name
211
+ when Class, String
212
+ property_name.to_s
213
+ when Property
214
+ property_name.class.to_s
215
+ else
216
+ raise ArgumentError
217
+ end
218
+
219
+ raise "Unknown property name: #{property_name}" unless @properties.has_key?(property_name.to_s)
220
+ c = 0
221
+ each_instance(property_name) { |svc|
222
+ c += 1
223
+ }
224
+ return c
225
+ end
226
+
227
+ def property_count
228
+ @properties.size
229
+ end
230
+
231
+ def each_instance(filter_prop_name=nil, &blk)
232
+ prop_obj = nil
233
+ if filter_prop_name.is_a? String
234
+ filter_prop_name = Util.build_const(filter_prop_name)
235
+ end
236
+
237
+ if filter_prop_name.is_a? Module
238
+ prop_obj = @properties.find { |k, v|
239
+ v.kind_of? filter_prop_name
240
+ }
241
+ if prop_obj.is_a? Array
242
+ prop_obj = prop_obj[1]
243
+ else
244
+ raise("Unknown property name: #{filter_prop_name.to_s}")
245
+ end
246
+ end
247
+
248
+ ary = []
249
+ if prop_obj.nil?
250
+ ary = @services.dup
251
+ else
252
+ ary = @services.find_all{|k, v| v.property.class == prop_obj.class }
253
+ ary = Hash[*ary.flatten]
254
+ end
255
+
256
+ ary.each {|k,v| blk.call v } if block_given?
257
+ ary
258
+ end
259
+ alias :select_instance :each_instance
260
+
261
+ def status=(new_status)
262
+ if @status != new_status
263
+ @status = new_status
264
+ @status_changed_at = Time.now
265
+ ED.fire_event(Event::ClusterStatusChanged.new(instance_id, new_status))
266
+ end
267
+ @status
268
+ end
269
+ thread_immutable_methods :status=
270
+
271
+ def size
272
+ @dg.size
273
+ end
274
+ alias :num_services :size
275
+
276
+ def properties
277
+ @properties
278
+ end
279
+
280
+ def instances
281
+ @services
282
+ end
283
+
284
+
285
+ def dump_status
286
+ r = {:name => self.class.to_s, :status => self.status, :instances=>{}, :properties=>{} }
287
+
288
+ instances.each { |k, i|
289
+ r[:instances][k]=i.dump_status
290
+ }
291
+ properties.each { |k, i|
292
+ r[:properties][k] = i.dump_status
293
+ r[:properties][k][:instances] = each_instance(i.class).collect{|k, v| k }
294
+ }
295
+
296
+ r
297
+ end
298
+ thread_immutable_methods :dump_status
299
+
300
+ private
301
+ def prepare
302
+ @dg = DependencyGraph.new(self)
303
+ @services = {}
304
+
305
+ @properties = {}
306
+ @name2prop ={}
307
+ @status = STATUS_OFFLINE
308
+ @status_changed_at = Time.now
309
+ @rule_engine = nil
310
+
311
+ @status_check_timer = EM::PeriodicTimer.new(5) {
312
+ update_cluster_status
313
+ }
314
+
315
+ @check_event_tickets = []
316
+ [Event::ServiceOnline, Event::ServiceOffline, Event::ServiceFailed].each { |evclass|
317
+ @check_event_tickets << ED.subscribe(evclass) { |event|
318
+ update_cluster_status
319
+ }
320
+ }
321
+
322
+ # ED.subscribe(Event::AgentTimedOut) { |event|
323
+ # svc_in_timedout_agent = service_cluster.instances.select { |k, i|
324
+ # if !i.agent.nil? && i.agent.agent_id == event.agent.agent_id
325
+ # i.status = Service::STATUS_FAIL
326
+ # end
327
+ # }
328
+
329
+ # }
330
+ end
331
+ thread_immutable_methods :prepare
332
+
333
+
334
+ def update_cluster_status
335
+ onlines = []
336
+ all_offline = false
337
+ onlines = self.instances.select { |k, i|
338
+ i.status == Service::STATUS_ONLINE
339
+ }
340
+ all_offline = self.instances.all? { |k, i|
341
+ i.status == Service::STATUS_OFFLINE
342
+ }
343
+ #Wakame.log.debug "online instances: #{onlines.size}, assigned instances: #{self.instances.size}"
344
+ if self.instances.size == 0 || all_offline
345
+ self.status = Service::ServiceCluster::STATUS_OFFLINE
346
+ elsif onlines.size == self.instances.size
347
+ self.status = Service::ServiceCluster::STATUS_ONLINE
348
+ elsif onlines.size > 0
349
+ self.status = Service::ServiceCluster::STATUS_PARTIAL_ONLINE
350
+ end
351
+
352
+ end
353
+ thread_immutable_methods :update_cluster_status
354
+
355
+
356
+ end
357
+
358
+
359
+ class DependencyGraph
360
+
361
+ def initialize(service_cluster)
362
+ @graph = Graph.new
363
+ @graph.add_vertex(0)
364
+ @service_cluster = service_cluster
365
+ @nodes = {}
366
+ end
367
+
368
+
369
+ def add_object(obj)
370
+ @nodes[obj.hash] = obj
371
+ @graph.add_edge(0, obj.hash)
372
+ self
373
+ end
374
+
375
+ def set_dependency(parent_obj, child_obj)
376
+ return if parent_obj == child_obj
377
+ @graph.add_edge(parent_obj.hash, child_obj.hash)
378
+ @graph.remove_edge(0, child_obj.hash) if @graph.has_edge?(0, child_obj.hash)
379
+ self
380
+ end
381
+
382
+ def size
383
+ @graph.size - 1
384
+ end
385
+
386
+ def parents(obj)
387
+ obj = case obj
388
+ when Class
389
+ obj.to_s.hash
390
+ when String
391
+ obj.hash
392
+ else
393
+ raise ArgumentError
394
+ end
395
+ @graph.parents(obj).collect { |hashid| property_obj(hashid) }
396
+ end
397
+
398
+ def children(obj)
399
+ obj = case obj
400
+ when Class
401
+ obj.to_s.hash
402
+ when String
403
+ obj.hash
404
+ else
405
+ raise ArgumentError
406
+ end
407
+ @graph.children(obj).collect { |hashid| property_obj(hashid) }
408
+ end
409
+
410
+ def levels(root=nil)
411
+ root = case root
412
+ when nil
413
+ 0
414
+ when Class
415
+ root.to_s.hash
416
+ when String
417
+ root.hash
418
+ else
419
+ raise ArgumentError
420
+ end
421
+ n=[]
422
+ @graph.level_layout(root).each { |l|
423
+ next if l.size == 1 && l[0] == 0
424
+ n << l.collect { |hashid| property_obj(hashid)}
425
+ #n << l.collect { |hashid| @nodes[hashid].to_s }
426
+ }
427
+ n
428
+ end
429
+
430
+ def each_level(root=nil, &blk)
431
+ root = case root
432
+ when nil
433
+ 0
434
+ when Class
435
+ root.to_s.hash
436
+ when String
437
+ root.hash
438
+ else
439
+ raise ArgumentError
440
+ end
441
+ @graph.level_layout(root).each { |l|
442
+ l.each { |hashid|
443
+ next if hashid == 0
444
+ blk.call(@service_cluster.properties[@nodes[hashid]])
445
+ }
446
+ }
447
+ end
448
+
449
+ private
450
+ def property_obj(hashid)
451
+ @service_cluster.properties[@nodes[hashid]]
452
+ end
453
+ end
454
+
455
+
456
+ class ServiceInstance
457
+ include ThreadImmutable
458
+ attr_reader :instance_id, :service_property, :agent, :service_cluster, :status_changed_at
459
+ attr_accessor :name
460
+ alias :cluster :service_cluster
461
+ alias :property :service_property
462
+
463
+ class << self
464
+ def instance_collection
465
+ @collection ||= {}
466
+ end
467
+ end
468
+
469
+ def initialize(service_property)
470
+ bind_thread
471
+ raise TypeError unless service_property.is_a?(Property)
472
+
473
+ @instance_id = Wakame.gen_id
474
+ @service_property = service_property
475
+ @status = Service::STATUS_OFFLINE
476
+ @status_changed_at = Time.now
477
+
478
+ self.class.instance_collection[@instance_id] = self
479
+ end
480
+
481
+ def update_status(new_status, changed_at=Time.now, fail_message=nil)
482
+ if @status != new_status
483
+ prev_status = @status
484
+ @status = new_status
485
+ @status_changed_at = changed_at
486
+
487
+ event = Event::ServiceStatusChanged.new(@instance_id, @service_property, new_status, prev_status)
488
+ event.time = @status_changed_at.dup
489
+ ED.fire_event(event)
490
+
491
+ tmp_event = nil
492
+ if prev_status != Service::STATUS_ONLINE && new_status == Service::STATUS_ONLINE
493
+ tmp_event = Event::ServiceOnline.new(self.instance_id, self.property)
494
+ tmp_event.time = @status_changed_at.dup
495
+ elsif prev_status != Service::STATUS_OFFLINE && new_status == Service::STATUS_OFFLINE
496
+ tmp_event = Event::ServiceOffline.new(self.instance_id, self.property)
497
+ tmp_event.time = @status_changed_at.dup
498
+ elsif prev_status != Service::STATUS_FAIL && new_status == Service::STATUS_FAIL
499
+ tmp_event = Event::ServiceFailed.new(self.instance_id, self.property, fail_message)
500
+ tmp_event.time = @status_changed_at.dup
501
+ end
502
+ ED.fire_event(tmp_event) if tmp_event
503
+
504
+ end
505
+ @status
506
+ end
507
+ thread_immutable_methods :update_status
508
+
509
+
510
+ def status
511
+ @status
512
+ end
513
+
514
+ def property
515
+ @service_property
516
+ end
517
+ def resource
518
+ @service_property
519
+ end
520
+
521
+ def type
522
+ @service_property.class
523
+ end
524
+
525
+ def bind_agent(agent)
526
+ return if agent.nil? || (@agent && agent.agent_id == @agent.agent_id)
527
+ raise "The agent (#{agent.agent_id}) was assigned same service already: #{property.class}" if agent.has_service_type?(property.class)
528
+
529
+ # UboundAgent & BoundAgent event occured only when the different agent obejct is assigned.
530
+ unbind_agent
531
+ @agent = agent
532
+ @agent.services[instance_id]=self
533
+
534
+ ED.fire_event(Event::ServiceBoundAgent.new(self, agent))
535
+ @agent
536
+ end
537
+ thread_immutable_methods :bind_agent
538
+
539
+ def unbind_agent
540
+ return nil if @agent.nil?
541
+ @agent.services.delete(instance_id)
542
+ old_item = @agent
543
+ @agent = nil
544
+ ED.fire_event(Event::ServiceUnboundAgent.new(self, old_item))
545
+ old_item
546
+ end
547
+ thread_immutable_methods :unbind_agent
548
+
549
+ def bind_cluster(cluster)
550
+ return if cluster.nil? || (@service_cluster && cluster.instance_id == @service_cluster.instance_id)
551
+ unbind_cluster
552
+ @service_cluster = cluster
553
+ ED.fire_event(Event::ServiceBoundCluster.new(self, cluster))
554
+ end
555
+ thread_immutable_methods :bind_cluster
556
+
557
+ def unbind_cluster
558
+ return if @service_cluster.nil?
559
+ old_item = @service_cluster
560
+ @service_cluster = nil
561
+ ED.fire_event(Event::ServiceUnboundCluster.new(self, old_item))
562
+ end
563
+ thread_immutable_methods :unbind_cluster
564
+
565
+ def export_binding
566
+ binding
567
+ end
568
+
569
+ def dump_status
570
+ ret = {:type => self.class.to_s, :status => status, :property => property.class.to_s, :instance_id => instance_id}
571
+ ret[:agent_id] = agent.agent_id if agent
572
+ ret
573
+ end
574
+ thread_immutable_methods :dump_status
575
+
576
+ def parent_instances
577
+ ary = []
578
+ @service_cluster.dg.parents(resource.class).each { |r|
579
+ @service_cluster.each_instance(r.class){ |i|
580
+ ary << i
581
+ }
582
+ }
583
+ ary.flatten
584
+ end
585
+
586
+ def child_instances
587
+ ary = []
588
+ @service_cluster.dg.children(resource.class).each { |r|
589
+ @service_cluster.each_instance(r.class){ |i|
590
+ ary << i
591
+ }
592
+ }
593
+ ary.flatten
594
+ end
595
+ end
596
+
597
+
598
+ class VmSpec
599
+ def self.define(&blk)
600
+ spec = self.new
601
+ spec.instance_eval(&blk)
602
+ spec
603
+ end
604
+
605
+ def initialize
606
+ @environments = {}
607
+ end
608
+
609
+ def current
610
+ environment(Wakame.config.environment)
611
+ end
612
+
613
+ def environment(klass_key, &blk)
614
+ envobj = @environments[klass_key]
615
+ if envobj.nil?
616
+ #klass = self.class.constants.find{ |c| c.to_s == klass_key.to_s }
617
+ if self.class.const_defined?(klass_key)
618
+ envobj = @environments[klass_key] = Wakame.new_([self.class.to_s, klass_key.to_s].join('::'))
619
+ else
620
+ raise "Undefined VM Spec template : #{klass_key}"
621
+ end
622
+ end
623
+
624
+ envobj.instance_eval(&blk) if blk
625
+
626
+ envobj
627
+ end
628
+
629
+ class Template
630
+ def self.inherited(klass)
631
+ klass.class_eval {
632
+ def self.default_attr_values
633
+ @default_attr_values ||= {}
634
+ end
635
+ def self.def_attribute(name, default_value=nil)
636
+ default_attr_values[name.to_sym]= default_value
637
+ attr_accessor(name)
638
+ end
639
+ }
640
+ end
641
+
642
+ def initialize
643
+ @attribute_keys=[]
644
+ self.class.default_attr_values.each { |n, v|
645
+ instance_variable_set("@#{n.to_s}", v)
646
+ #self.instance_eval %Q{ #{n} = #{v} }
647
+ @attribute_keys << n
648
+ }
649
+ end
650
+
651
+ def attrs
652
+ a={}
653
+ @attribute_keys.each { |k|
654
+ a[k.to_sym]=instance_variable_get("@#{k.to_s}")
655
+ }
656
+ a
657
+ end
658
+
659
+ def satisfy?(agent)
660
+ true
661
+ end
662
+ end
663
+
664
+ class EC2 < Template
665
+ AWS_VERSION=''
666
+ def_attribute :instance_type, 'm1.small'
667
+ def_attribute :availability_zone
668
+ def_attribute :key_name
669
+ def_attribute :security_groups, []
670
+ end
671
+
672
+ class StandAlone < Template
673
+ end
674
+ end
675
+
676
+
677
+ class Property
678
+ include AttributeHelper
679
+ attr_accessor :check_time, :vm_spec
680
+ def_attribute :duplicable, true
681
+ def_attribute :min_instances, 1
682
+ def_attribute :max_instances, 1
683
+ def_attribute :startup, true
684
+ def_attribute :require_agent, true
685
+
686
+ def initialize(check_time=5)
687
+ @check_time = check_time
688
+ @vm_spec = VmSpec.define {
689
+ environment(:EC2) { |ec2|
690
+ ec2.instance_type = 'm1.small'
691
+ ec2.availability_zone = 'us-east-1c'
692
+ ec2.security_groups = ['default']
693
+ }
694
+
695
+ environment(:StandAlone) {
696
+ }
697
+ }
698
+ end
699
+
700
+ def basedir
701
+ File.join(Wakame.config.root_path, 'cluster', 'resources', Util.snake_case(self.class))
702
+ end
703
+
704
+ def dump_status
705
+ {:type => self.class.to_s, :min_instances => min_instances, :max_instances=> max_instances,
706
+ :duplicable=>duplicable
707
+ }
708
+ end
709
+
710
+ def start(service_instance, action); end
711
+ def stop(service_instance, action); end
712
+ def reload(service_instance, action); end
713
+
714
+ def render_config(template)
715
+ end
716
+
717
+ #def before_start(service_instance, action)
718
+ #end
719
+ #def after_start(service_instance, action)
720
+ #end
721
+ #def before_stop(service_instance, action)
722
+ #end
723
+ #def after_stop(service_instance, action)
724
+ #end
725
+
726
+ def on_child_changed(service_instance, action)
727
+ end
728
+ def on_parent_changed(service_instance, action)
729
+ end
730
+
731
+ end
732
+
733
+ Resource = Property
734
+ end
735
+ end
736
+
737
+ module Wakame
738
+ module Service
739
+
740
+
741
+ module ApacheBasicProps
742
+ attr_accessor :listen_port, :listen_port_https, :server_root
743
+
744
+ end
745
+
746
+ class MySQL_Slave < Property
747
+ attr_reader :basedir, :mysqld_datadir, :mysqld_port, :mysqld_server_id, :mysqld_log_bin, :ebs_volume, :ebs_device
748
+
749
+ def initialize
750
+ super()
751
+ @template = ConfigurationTemplate::MySQLSlaveTemplate.new()
752
+ @basedir = '/home/wakame/mysql'
753
+
754
+ @mysqld_server_id = 2 # dynamic
755
+ @mysqld_port = 3307
756
+ @mysqld_datadir = File.expand_path('data-slave', @basedir)
757
+
758
+ @ebs_volume = 'vol-38bc5f51' # master volume_id
759
+ @ebs_device = '/dev/sdn' # slave mount point
760
+ @ebs_mount_option = 'noatime'
761
+
762
+ @mysqld_master_host = '10.249.2.115'
763
+ @mysqld_master_user = 'wakame-repl'
764
+ @mysqld_master_pass = 'wakame-slave'
765
+ @mysqld_master_port = 3306
766
+ @mysqld_master_datadir = File.expand_path('data', @basedir)
767
+
768
+ @duplicable = false
769
+ end
770
+
771
+ def before_start(svc, action)
772
+ vm_manipulator = VmManipulator.create
773
+
774
+ Wakame.log.debug("mkdir #{@mysqld_datadir}")
775
+ system("[ -d #{@mysqld_datadir} ] || mkdir -p #{@mysqld_datadir}")
776
+ Wakame.log.debug("[ -b #{@ebs_device} ]")
777
+ system("[ -b #{@ebs_device} ]")
778
+ if $? == 0
779
+ Wakame.log.debug("The EBS volume(slave) device is not ready to attach: #{@ebs_device}")
780
+ return
781
+ end
782
+
783
+ volume_map = vm_manipulator.describe_volume(@ebs_volume)
784
+ Wakame.log.debug("describe_volume(#{@ebs_volume}): #{volume_map.inspect}")
785
+ if volume_map['status'] == 'in-use'
786
+ # Nothin to be done
787
+ else
788
+ Wakame.log.debug("The EBS volume(slave) is not ready to attach: #{@ebs_volume}")
789
+ return
790
+ end
791
+
792
+ system("echo show master status | /usr/bin/mysql -h#{@mysqld_master_host} -P#{@mysqld_master_port} -u#{@mysqld_master_user} -p#{@mysqld_master_pass}")
793
+ if $? != 0
794
+ raise "Can't connect mysql master: #{@mysqld_master_host}:#{@mysqld_master_port}"
795
+ end
796
+
797
+ system("echo 'FLUSH TABLES WITH READ LOCK;' | /usr/bin/mysql -h#{@mysqld_master_host} -P#{@mysqld_master_port} -u#{@mysqld_master_user} -p#{@mysqld_master_pass} -s")
798
+ master_status = `echo show master status | /usr/bin/mysql -h#{@mysqld_master_host} -P#{@mysqld_master_port} -u#{@mysqld_master_user} -p#{@mysqld_master_pass} -s`.to_s.split(/\t/)[0..1]
799
+ # p master_status
800
+
801
+ # mysql/data/master.info
802
+ master_infos = []
803
+ master_infos << 14
804
+ master_infos << master_status[0]
805
+ master_infos << master_status[1]
806
+ master_infos << @mysqld_master_host
807
+ master_infos << @mysqld_master_user
808
+ master_infos << @mysqld_master_pass
809
+ master_infos << @mysqld_master_port
810
+ master_infos << 60
811
+ master_infos << 0
812
+ master_infos << ""
813
+ master_infos << ""
814
+ master_infos << ""
815
+ master_infos << ""
816
+ master_infos << ""
817
+ master_infos << ""
818
+
819
+ tmp_output_basedir = File.expand_path(Wakame.gen_id, "/tmp")
820
+ FileUtils.mkdir_p tmp_output_basedir
821
+ master_info = File.expand_path('master.info', tmp_output_basedir)
822
+ file = File.new(master_info, "w")
823
+ file.puts(master_infos.join("\n"))
824
+ file.chmod(0664)
825
+ file.close
826
+
827
+ 3.times do |i|
828
+ system("/bin/sync")
829
+ sleep 1.0
830
+ end
831
+
832
+ Wakame.log.debug("scp -i #{Wakame.config.ssh_private_key} -o \"UserKnownHostsFile #{Wakame.config.ssh_known_hosts}\" #{master_info} root@#{@mysqld_master_host}:#{@mysqld_master_datadir}/" )
833
+ system("scp -i #{Wakame.config.ssh_private_key} -o \"UserKnownHostsFile #{Wakame.config.ssh_known_hosts}\" #{master_info} root@#{@mysqld_master_host}:#{@mysqld_master_datadir}/" )
834
+ Wakame.log.debug("ssh -i #{Wakame.config.ssh_private_key} -o \"UserKnownHostsFile #{Wakame.config.ssh_known_hosts}\" root@#{@mysqld_master_host} chown mysql:mysql #{@mysqld_master_datadir}/master.info" )
835
+ system("ssh -i #{Wakame.config.ssh_private_key} -o \"UserKnownHostsFile #{Wakame.config.ssh_known_hosts}\" root@#{@mysqld_master_host} chown mysql:mysql #{@mysqld_master_datadir}/master.info" )
836
+
837
+ 3.times do |i|
838
+ system("/bin/sync")
839
+ sleep 1.0
840
+ end
841
+
842
+ FileUtils.rm_rf tmp_output_basedir
843
+
844
+ # 2. snapshot
845
+ Wakame.log.debug("create_snapshot (#{@ebs_volume})")
846
+ snapshot_map = vm_manipulator.create_snapshot(@ebs_volume)
847
+ 16.times do |i|
848
+ Wakame.log.debug("describe_snapshot(#{snapshot_map.snapshotId}) ... #{i}")
849
+ snapshot_map = vm_manipulator.describe_snapshot(snapshot_map["snapshotId"])
850
+ if snapshot_map["status"] == "completed"
851
+ break
852
+ end
853
+ sleep 1.0
854
+ end
855
+ if snapshot_map["status"] != "completed"
856
+ raise "#{snapshot_map.snapshotId} status is #{snapshot_map.status}"
857
+ end
858
+
859
+ # 3. unlock mysql-master
860
+ system("echo 'UNLOCK TABLES;' | /usr/bin/mysql -h#{@mysqld_master_host} -P#{@mysqld_master_port} -u#{@mysqld_master_user} -p#{@mysqld_master_pass}")
861
+
862
+ # create volume /dev/xxxx
863
+ Wakame.log.debug("create_volume_from_snapshot(#{volume_map.availabilityZone}, #{snapshot_map.snapshotId})")
864
+ created_volume_from_snapshot_map = vm_manipulator.create_volume_from_snapshot(volume_map["availabilityZone"], snapshot_map["snapshotId"])
865
+ volume_from_snapshot_map = created_volume_from_snapshot_map
866
+ 16.times do |i|
867
+ Wakame.log.debug("describe_snapshot(#{snapshot_map.snapshotId}) ... #{i}")
868
+ volume_from_snapshot_map = vm_manipulator.describe_snapshot(snapshot_map["snapshotId"])
869
+ if volume_from_snapshot_map["status"] == "completed"
870
+ break
871
+ end
872
+ sleep 1.0
873
+ end
874
+ if volume_from_snapshot_map["status"] != "completed"
875
+ raise "#{volume_from_snapshot_map.snapshotId} status is #{volume_from_snapshot_map.status}"
876
+ end
877
+
878
+ # attach volume
879
+ attach_volume_map = vm_manipulator.attach_volume(svc.agent.agent_id, created_volume_from_snapshot_map["volumeId"], @ebs_device)
880
+ 16.times do |i|
881
+ Wakame.log.debug("describe_volume(#{attach_volume_map.volumeId}) ... #{i}")
882
+ attach_volume_map = vm_manipulator.describe_volume(created_volume_from_snapshot_map["volumeId"])
883
+ if attach_volume_map["status"] == "in-use"
884
+ break
885
+ end
886
+ sleep 1.0
887
+ end
888
+ if attach_volume_map["status"] != "in-use"
889
+ raise "#{attach_volume_map.volumeId} status is #{attach_volume_map.status}"
890
+ end
891
+ end
892
+
893
+ def start
894
+ mount_point_dev=`df "#{@mysqld_datadir}" | awk 'NR==2 {print $1}'`
895
+ if mount_point_dev != @ebs_device
896
+ Wakame.log.debug("Mounting EBS volume: #{@ebs_device} as #{@mysqld_datadir} (with option: #{@ebs_mount_option})")
897
+ system("/bin/mount -o #{@ebs_mount_option} #{@ebs_device} #{@mysqld_datadir}")
898
+ end
899
+ system(Wakame.config.root + "/config/init.d/mysql-slave start")
900
+ end
901
+
902
+ def check
903
+ system("/usr/bin/mysqladmin --defaults-file=/home/wakame/config/mysql-slave/my-slave.cnf ping > /dev/null")
904
+ return false if $? != 0
905
+ true
906
+ end
907
+
908
+ def stop
909
+ system(Wakame.config.root + "/config/init.d/mysql-slave stop")
910
+ end
911
+ end
912
+
913
+ end
914
+ end