wakame 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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