wakame 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/History.txt +8 -0
  2. data/Rakefile +3 -3
  3. data/VERSION +1 -1
  4. data/app_generators/wakame/templates/cluster/resources/markers/http_application_server.rb +3 -0
  5. data/app_generators/wakame/templates/cluster/resources/markers/http_asset_server.rb +2 -0
  6. data/app_generators/wakame/templates/cluster/resources/markers/http_server.rb +9 -0
  7. data/app_generators/wakame/templates/config/cluster.rb +36 -99
  8. data/app_generators/wakame/templates/config/init.d/wakame-agent +3 -3
  9. data/app_generators/wakame/templates/config/init.d/wakame-master +3 -3
  10. data/app_generators/wakame/wakame_generator.rb +5 -1
  11. data/contrib/imagesetup.sh +9 -5
  12. data/lib/ext/uri.rb +13 -0
  13. data/lib/wakame/action.rb +46 -21
  14. data/lib/wakame/{rule_engine.rb → action_manager.rb} +148 -36
  15. data/lib/wakame/actions/deploy_config.rb +35 -0
  16. data/lib/wakame/actions/launch_cluster.rb +8 -10
  17. data/lib/wakame/actions/launch_vm.rb +26 -20
  18. data/lib/wakame/actions/migrate_service.rb +30 -19
  19. data/lib/wakame/actions/notify_child_changed.rb +34 -0
  20. data/lib/wakame/actions/notify_parent_changed.rb +34 -0
  21. data/lib/wakame/actions/propagate_resource.rb +27 -0
  22. data/lib/wakame/actions/propagate_service.rb +27 -0
  23. data/lib/wakame/actions/reload_service.rb +21 -9
  24. data/lib/wakame/actions/shutdown_cluster.rb +3 -3
  25. data/lib/wakame/actions/shutdown_vm.rb +14 -5
  26. data/lib/wakame/actions/start_service.rb +53 -47
  27. data/lib/wakame/actions/stop_service.rb +35 -23
  28. data/lib/wakame/actor/system.rb +6 -3
  29. data/lib/wakame/agent.rb +29 -7
  30. data/lib/wakame/amqp_client.rb +26 -5
  31. data/lib/wakame/command/action_status.rb +7 -7
  32. data/lib/wakame/command/actor.rb +10 -10
  33. data/lib/wakame/command/import_cluster_config.rb +10 -0
  34. data/lib/wakame/command/launch_cluster.rb +2 -2
  35. data/lib/wakame/command/launch_vm.rb +3 -3
  36. data/lib/wakame/command/migrate_service.rb +7 -7
  37. data/lib/wakame/command/propagate_resource.rb +42 -0
  38. data/lib/wakame/command/propagate_service.rb +21 -19
  39. data/lib/wakame/command/reload_service.rb +3 -13
  40. data/lib/wakame/command/shutdown_cluster.rb +2 -2
  41. data/lib/wakame/command/start_service.rb +14 -0
  42. data/lib/wakame/command/status.rb +32 -10
  43. data/lib/wakame/command/stop_service.rb +27 -21
  44. data/lib/wakame/command.rb +19 -3
  45. data/lib/wakame/command_queue.rb +87 -67
  46. data/lib/wakame/configuration.rb +6 -0
  47. data/lib/wakame/event.rb +17 -0
  48. data/lib/wakame/event_dispatcher.rb +32 -23
  49. data/lib/wakame/graph.rb +2 -1
  50. data/lib/wakame/initializer.rb +11 -8
  51. data/lib/wakame/master.rb +327 -209
  52. data/lib/wakame/monitor/agent.rb +5 -1
  53. data/lib/wakame/monitor/service.rb +6 -5
  54. data/lib/wakame/packets.rb +13 -21
  55. data/lib/wakame/runner/administrator_command.rb +383 -264
  56. data/lib/wakame/runner/agent.rb +1 -5
  57. data/lib/wakame/runner/master.rb +0 -3
  58. data/lib/wakame/service.rb +817 -538
  59. data/lib/wakame/status_db.rb +383 -0
  60. data/lib/wakame/template.rb +27 -130
  61. data/lib/wakame/trigger.rb +10 -18
  62. data/lib/wakame/triggers/instance_count_update.rb +1 -1
  63. data/lib/wakame/triggers/load_history.rb +1 -1
  64. data/lib/wakame/triggers/maintain_ssh_known_hosts.rb +8 -5
  65. data/lib/wakame/triggers/shutdown_unused_vm.rb +1 -1
  66. data/lib/wakame/util.rb +64 -55
  67. data/lib/wakame.rb +4 -0
  68. data/tests/test_action_manager.rb +111 -0
  69. data/tests/test_service.rb +128 -23
  70. data/tests/test_status_db.rb +82 -0
  71. data/tests/test_uri_amqp.rb +10 -0
  72. data/wakame_generators/resource/templates/apache_app/apache_app.rb +19 -18
  73. data/wakame_generators/resource/templates/apache_app/conf/apache2.conf +14 -2
  74. data/wakame_generators/resource/templates/apache_app/conf/system-app.conf +1 -1
  75. data/wakame_generators/resource/templates/apache_app/conf/vh/aaa.test.conf +9 -0
  76. data/wakame_generators/resource/templates/apache_lb/apache_lb.rb +21 -20
  77. data/wakame_generators/resource/templates/apache_lb/conf/apache2.conf +14 -2
  78. data/wakame_generators/resource/templates/apache_lb/conf/system-lb.conf +17 -2
  79. data/wakame_generators/resource/templates/apache_lb/conf/vh/aaa.test.conf +37 -0
  80. data/wakame_generators/resource/templates/apache_www/apache_www.rb +20 -18
  81. data/wakame_generators/resource/templates/apache_www/conf/apache2.conf +14 -2
  82. data/wakame_generators/resource/templates/apache_www/conf/system-www.conf +1 -1
  83. data/wakame_generators/resource/templates/apache_www/conf/vh/aaa.test.conf +9 -0
  84. data/wakame_generators/resource/templates/ec2_elastic_ip/ec2_elastic_ip.rb +6 -8
  85. data/wakame_generators/resource/templates/ec2_elb/ec2_elb.rb +7 -6
  86. data/wakame_generators/resource/templates/memcached/conf/memcached.conf +47 -0
  87. data/wakame_generators/resource/templates/memcached/init.d/memcached +61 -0
  88. data/wakame_generators/resource/templates/memcached/memcached.rb +73 -0
  89. data/wakame_generators/resource/templates/mysql_master/conf/my.cnf +5 -7
  90. data/wakame_generators/resource/templates/mysql_master/mysql_master.rb +35 -34
  91. data/wakame_generators/resource/templates/mysql_slave/conf/my.cnf +6 -6
  92. data/wakame_generators/resource/templates/mysql_slave/mysql_slave.rb +21 -24
  93. data/wakame_generators/resource/templates/nginx/conf/nginx.conf +17 -27
  94. data/wakame_generators/resource/templates/nginx/conf/vh/aaa.test.conf +30 -0
  95. data/wakame_generators/resource/templates/nginx/nginx.rb +18 -18
  96. metadata +34 -21
  97. data/lib/wakame/actions/propagate_instances.rb +0 -70
  98. data/lib/wakame/manager/commands.rb +0 -134
  99. data/lib/wakame/rule.rb +0 -116
  100. data/lib/wakame/triggers/process_command.rb +0 -41
  101. data/tests/test_rule_engine.rb +0 -127
  102. data/wakame_generators/resource/templates/apache_app/conf/sites-app.conf +0 -23
  103. data/wakame_generators/resource/templates/apache_lb/conf/sites-lb.conf +0 -54
  104. data/wakame_generators/resource/templates/apache_www/conf/sites-www.conf +0 -23
@@ -12,6 +12,8 @@ module Wakame
12
12
  class ServicePropagationError < ServiceError; end
13
13
 
14
14
 
15
+ STATUS_TERMINATE = -2
16
+ STATUS_INIT = -1
15
17
  STATUS_OFFLINE = 0
16
18
  STATUS_ONLINE = 1
17
19
  STATUS_UNKNOWN = 2
@@ -20,647 +22,877 @@ module Wakame
20
22
  STATUS_STOPPING = 5
21
23
  STATUS_RELOADING = 6
22
24
  STATUS_MIGRATING = 7
23
-
24
- class Agent
25
- include ThreadImmutable
26
- include AttributeHelper
25
+ STATUS_ENTERING = 8
26
+ STATUS_QUITTING = 9
27
+
28
+ # Data model for agent
29
+ # Status life cycle:
30
+ # STATUS_INIT -> [STATUS_ONLINE|STATUS_OFFLINE|STATUS_TIMEOUT] -> STATUS_END
31
+ class Agent < StatusDB::Model
32
+ STATUS_END = -1
33
+ STATUS_INIT = -2
27
34
  STATUS_OFFLINE = 0
28
35
  STATUS_ONLINE = 1
29
36
  STATUS_UNKNOWN = 2
30
37
  STATUS_TIMEOUT = 3
31
38
 
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=
39
+ property :last_ping_at
40
+ property :vm_attr
41
+ property :root_path
42
+ property :status, {:read_only=>true, :default=>STATUS_INIT}
43
+ property :reported_services, {:read_only=>true, :default=>{}}
44
+ property :cloud_host_id
45
+
46
+ def mapped?
47
+ !self.cloud_host_id.nil?
48
+ end
49
+
50
+ def id=(agent_id)
51
+ @id = agent_id
52
+ end
34
53
 
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
54
+ def id
55
+ @id || raise("Agent.id is unset: #{self}")
41
56
  end
42
57
 
43
58
  def agent_ip
44
- attr[:local_ipv4]
59
+ vm_attr[:local_ipv4]
45
60
  end
46
61
 
47
- def [](key)
48
- attr[key]
62
+ def vm_id
63
+ vm_attr[:instance_id]
49
64
  end
50
65
 
51
- attr_reader :status
66
+ # Tentative...
67
+ def last_ping_at_time
68
+ require 'time'
69
+ Time.parse(last_ping_at)
70
+ end
71
+
72
+ def update_status(new_status)
73
+ if @status != new_status
74
+ @status = new_status
75
+ self.save
52
76
 
53
- def status=(status)
54
- if @status != status
55
- @status = status
56
77
  ED.fire_event(Event::AgentStatusChanged.new(self))
57
78
  # Send status specific event
58
- case status
79
+ case @status
59
80
  when STATUS_TIMEOUT
60
81
  ED.fire_event(Event::AgentTimedOut.new(self))
61
82
  end
62
83
  end
63
- @status
64
84
  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
85
+
86
+
87
+ def renew_reported_services(svc_id_list)
88
+ reported_services.clear
89
+ svc_id_list.each { |svc_id, data|
90
+ reported_services[svc_id] = data
83
91
  }
84
92
  end
85
93
 
94
+ def has_resource_type?(key)
95
+ res_id = key.is_a?(ServiceInstance) ? key.resource.id : Resource.id(key)
86
96
 
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
97
+ reported_services.keys.any? { |k|
98
+ svc = ServiceInstance.find(k)
99
+ svc.resource.id == res_id
90
100
  }
91
101
  end
92
-
102
+
103
+ def terminate
104
+ end
93
105
  end
94
106
 
107
+ class AgentPool < StatusDB::Model
108
+ property :group_observed, {:default=>{}}
109
+ property :group_active, {:default=>{}}
95
110
 
96
- class ServiceCluster
97
- include ThreadImmutable
111
+ ID=self.to_s
98
112
 
99
- attr_reader :dg, :instance_id, :status_changed_at, :rule_engine, :master
100
- attr_reader :status, :lock_queue
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
- @lock_queue = LockQueue.new(self)
112
- prepare
113
-
114
- instance_eval(&blk) if blk
113
+ def self.instance
114
+ a = self.find(ID)
115
+ if a.nil?
116
+ a = self.new
117
+ a.save
118
+ end
119
+ a
115
120
  end
116
121
 
117
- def define_rule(&blk)
118
- @rule_engine ||= RuleEngine.new(self)
119
-
120
- blk.call(@rule_engine)
121
- end
122
-
123
- def add_resource(resource, name=nil)
124
- #if name.nil? || @name2prop.has_key? name
125
- # name = "#{resource.class.to_s}#{name2prop.size + 1}"
126
- #end
127
- raise ArgumentError unless resource.is_a? Resource
128
- raise "Duplicate resource type registration" if @properties.has_key? resource.class.to_s
129
- @properties[resource.class.to_s]=resource
130
- @dg.add_object(resource.class.to_s)
122
+ def self.reset
123
+ ap = self.instance
131
124
 
132
- #name
133
- end
134
- thread_immutable_methods :add_resource
135
-
136
- def set_dependency(prop_name1, prop_name2)
137
- prop1 = @properties[prop_name1.to_s]
138
- prop2 = @properties[prop_name2.to_s]
139
- return unless prop1.is_a?(Property) && prop2.is_a?(Property) && prop1 != prop2
140
- @dg.set_dependency(prop_name1.to_s, prop_name2.to_s)
141
- end
142
- thread_immutable_methods :set_dependency
125
+ nil_cloud_host_id = lambda { |agent_id|
126
+ agent = Agent.find(agent_id)
127
+ agent.cloud_host_id = nil
128
+ agent.save
129
+ }
143
130
 
144
- def included_instance?(service_instance_id)
145
- @services.has_key? service_instance_id
131
+ ap.group_observed.keys.each &nil_cloud_host_id
132
+ ap.group_active.keys.each &nil_cloud_host_id
133
+ ap.save
146
134
  end
147
135
 
148
136
 
149
- def shutdown
137
+ def id
138
+ ID
150
139
  end
151
- thread_immutable_methods :shutdown
152
140
 
153
- # Create service instance objects which will be equivalent with the number min_instance.
154
- # The agents are not assigned at this point.
155
- def launch
156
- @properties.each { |n, p|
157
- count = instance_count(p)
158
- if p.min_instances > count
159
- (p.min_instances - count).times {
160
- propagate(p.class)
161
- }
162
- end
163
- }
141
+ def create_or_find(agent_id)
142
+ agent = Service::Agent.find(agent_id)
143
+ if agent.nil?
144
+ agent = Service::Agent.new
145
+ agent.id = agent_id
146
+ Wakame.log.debug("#{self.class}: Created new agent object with Agent ID: #{agent_id}")
147
+ end
148
+ agent
164
149
  end
165
- thread_immutable_methods :launch
166
150
 
167
- def destroy(service_instance_id)
168
- raise("Unknown service instance : #{service_instance_id}") unless included_instance?(service_instance_id)
169
- svc_inst = @services[service_instance_id]
170
- old_agent = svc_inst.unbind_agent
171
- svc_inst.unbind_cluster
172
- @services.delete(service_instance_id)
173
- if old_agent
174
- Wakame.log.debug("#{svc_inst.property.class}(#{svc_inst.instance_id}) has been destroied from Agent #{old_agent.agent_id}")
151
+ def register_as_observed(agent)
152
+ agent_id = agent.id
153
+ if group_observed.has_key?(agent_id)
175
154
  else
176
- Wakame.log.debug("#{svc_inst.property.class}(#{svc_inst.instance_id}) has been destroied.")
155
+ if group_active.has_key?(agent_id)
156
+ # Move the reference from unregistered group to the registered group.
157
+ group_active.delete(agent_id)
158
+ group_observed[agent_id]=1
159
+ else
160
+ # The agent is going to be registered at first time.
161
+ group_observed[agent_id]=1
162
+ end
163
+
164
+ self.save
165
+ Wakame.log.debug("#{self.class}: Register agent to observed group: #{agent_id}")
177
166
  end
178
167
  end
179
- thread_immutable_methods :destroy
180
168
 
181
- def propagate(property_name, force=false)
182
- property_name = case property_name
183
- when Class, String
184
- property_name.to_s
185
- when Property
186
- property_name.class.to_s
187
- else
188
- raise ArgumentError
189
- end
190
- prop = @properties[property_name.to_s] || raise("Unknown property name: #{property_name.to_s}")
191
- if force == false
192
- instnum = instance_count(property_name)
193
- if instnum >= prop.max_instances
194
- Wakame.log.info("#{prop.class} has been reached max_instance limit: max=#{prop.max_instance}")
195
- raise ServicePropagationError, "#{prop.class} has been reached to max_instance limit"
169
+ def register(agent)
170
+ raise ArgumentError unless agent.is_a?(Agent)
171
+ agent_id = agent.id
172
+ if group_active.has_key?(agent_id)
173
+ else
174
+ if group_observed.has_key?(agent_id)
175
+ # Move the reference from unregistered group to the registered group.
176
+ group_observed.delete(agent_id)
177
+ group_active[agent_id]=1
178
+ else
179
+ # The agent is going to be registered at first time.
180
+ group_active[agent_id]=1
196
181
  end
182
+
183
+ self.save
184
+
185
+ Wakame.log.debug("#{self.class}: Register agent to active group: #{agent_id}")
186
+ ED.fire_event(Event::AgentMonitored.new(agent))
197
187
  end
188
+ end
189
+
190
+ def unregister(agent)
191
+ raise ArgumentError unless agent.is_a?(Agent)
192
+ agent_id = agent.id
198
193
 
199
- svc_inst = Service::ServiceInstance.new(prop)
200
- svc_inst.bind_cluster(self)
201
- #svc_inst.bind_agent(agent) if agent
194
+ unregistered = false
195
+ if group_active.has_key?(agent_id)
196
+ group_active.delete(agent_id)
197
+ unregistered = true
198
+ end
199
+
200
+ if group_observed.has_key?(agent_id)
201
+ group_observed.delete(agent_id)
202
+ unregistered = true
203
+ end
202
204
 
203
- @services[svc_inst.instance_id]=svc_inst
204
- svc_inst
205
+ if unregistered
206
+ self.save
207
+
208
+ Wakame.log.debug("#{self.class}: Unregister agent: #{agent.id}")
209
+ ED.fire_event(Event::AgentUnMonitored.new(agent))
210
+ end
205
211
  end
206
- thread_immutable_methods :propagate
207
212
 
208
- def instance_count(property_name=nil)
209
- return @services.size if property_name.nil?
213
+ def find_agent(agent_id)
214
+ raise "The agent ID \"#{agent_id}\" is not registered in the pool" unless group_active.has_key? agent_id
215
+ Agent.find(agent_id) || raise("The agent ID #{agent_id} is registered. but not in the database.")
216
+ end
210
217
 
211
- property_name = case property_name
212
- when Class, String
213
- property_name.to_s
214
- when Property
215
- property_name.class.to_s
216
- else
217
- raise ArgumentError
218
- end
218
+ end
219
219
 
220
- raise "Unknown property name: #{property_name}" unless @properties.has_key?(property_name.to_s)
221
- c = 0
222
- each_instance(property_name) { |svc|
223
- c += 1
224
- }
225
- return c
226
- end
220
+ class CloudHost < StatusDB::Model
221
+ property :agent_id
227
222
 
228
- def property_count
229
- @properties.size
223
+ # cluster_id will not become nil since ServiceCluster has responsibility for the lifecycle of this class.
224
+ property :cluster_id
225
+
226
+ # Virtual Machine attributes specified by Resource object.
227
+ property :vm_attr, {:default=>{}}
228
+
229
+ def mapped?
230
+ !self.agent_id.nil?
230
231
  end
232
+
233
+ def map_agent(agent)
234
+ raise ArgumentError unless agent.is_a?(Agent)
235
+ raise "Ensure to call unmap_agent() prior to mapping new agent" if self.mapped?
236
+ raise "The agent \"#{agent.id}\" is already mapped to the cloud host: #{agent.cloud_host_id}" if agent.mapped?
231
237
 
232
- def each_instance(filter_prop_name=nil, &blk)
233
- prop_obj = nil
234
- if filter_prop_name.is_a? String
235
- filter_prop_name = Util.build_const(filter_prop_name)
236
- end
238
+ self.agent_id = agent.id
239
+ agent.cloud_host_id = self.id
237
240
 
238
- if filter_prop_name.is_a? Module
239
- prop_obj = @properties.find { |k, v|
240
- v.kind_of? filter_prop_name
241
- }
242
- if prop_obj.is_a? Array
243
- prop_obj = prop_obj[1]
244
- else
245
- raise("Unknown property name: #{filter_prop_name.to_s}")
241
+ self.save
242
+ agent.save
243
+ end
244
+
245
+ def unmap_agent()
246
+ if mapped?
247
+ agent = Agent.find(self.agent_id)
248
+ if agent && self.agent_id == agent.id
249
+ agent.cloud_host_id = nil
250
+ agent.save
246
251
  end
247
- end
248
252
 
249
- ary = []
250
- if prop_obj.nil?
251
- ary = @services.dup
252
- else
253
- ary = @services.find_all{|k, v| v.property.class == prop_obj.class }
254
- ary = Hash[*ary.flatten]
253
+ self.agent_id = nil
254
+ self.save
255
255
  end
256
+ end
256
257
 
257
- ary.each {|k,v| blk.call v } if block_given?
258
- ary
258
+ def agent
259
+ raise "#{self.class}: Agent is not mapped yet to this cloud host \"#{self.id}\"." unless mapped?
260
+ Agent.find(self.agent_id) || raise("#{self.class}: Could not find the mapped agent for #{self.class}.id=\"#{self.id}\"")
259
261
  end
260
- alias :select_instance :each_instance
261
262
 
262
- def status=(new_status)
263
- if @status != new_status
264
- @status = new_status
265
- @status_changed_at = Time.now
266
- ED.fire_event(Event::ClusterStatusChanged.new(instance_id, new_status))
267
- end
268
- @status
263
+ def vm_spec
264
+ spec = VmSpec.current
265
+ spec.table = self.vm_attr
266
+ spec
269
267
  end
270
- thread_immutable_methods :status=
271
268
 
272
- def size
273
- @dg.size
269
+
270
+ # Delegate methods for Agent class
271
+
272
+ def status
273
+ self.agent.status
274
274
  end
275
- alias :num_services :size
276
275
 
277
- def properties
278
- @properties
276
+ def reported_services
277
+ self.agent.reported_services
279
278
  end
280
279
 
281
- def instances
282
- @services
280
+ def root_path
281
+ self.agent.root_path
283
282
  end
284
283
 
284
+ def agent_ip
285
+ self.agent.agent_ip
286
+ end
285
287
 
286
- def dump_status
287
- r = {:name => self.class.to_s, :status => self.status, :instances=>{}, :properties=>{} }
288
-
289
- instances.each { |k, i|
290
- r[:instances][k]=i.dump_status
291
- }
292
- properties.each { |k, i|
293
- r[:properties][k] = i.dump_attrs
294
- r[:properties][k][:instances] = each_instance(i.class).collect{|k, v| k }
288
+ def has_resource_type?(key)
289
+ res_id = key.is_a?(ServiceInstance) ? key.resource.id : Resource.id(key)
290
+
291
+ assigned_services.any? { |k|
292
+ svc = ServiceInstance.find(k)
293
+ svc.resource.id == res_id
295
294
  }
295
+ end
296
296
 
297
- r
297
+ def assigned_services()
298
+ cluster = ServiceCluster.find(self.cluster_id)
299
+ cluster.services.keys.find_all { |svc_id| ServiceInstance.find(svc_id).cloud_host_id == self.id }
298
300
  end
299
- thread_immutable_methods :dump_status
300
-
301
- private
302
- def prepare
303
- @dg = DependencyGraph.new(self)
304
- @services = {}
305
-
306
- @properties = {}
307
- @name2prop ={}
308
- @status = STATUS_OFFLINE
309
- @status_changed_at = Time.now
310
- @rule_engine = nil
311
301
 
312
- @status_check_timer = EM::PeriodicTimer.new(5) {
313
- update_cluster_status
314
- }
302
+ def assigned_resources()
303
+ assigned_services.map { |svc_id|
304
+ ServiceInstance.find(svc_id).resource
305
+ }.uniq
306
+ end
315
307
 
316
- @check_event_tickets = []
317
- [Event::ServiceOnline, Event::ServiceOffline, Event::ServiceFailed].each { |evclass|
318
- @check_event_tickets << ED.subscribe(evclass) { |event|
319
- update_cluster_status
320
- }
321
- }
308
+ def validate_on_save
309
+ raise "#{self.class}.cluster_id property can't be nil." if self.cluster_id.nil?
310
+ end
322
311
 
323
- # ED.subscribe(Event::AgentTimedOut) { |event|
324
- # svc_in_timedout_agent = service_cluster.instances.select { |k, i|
325
- # if !i.agent.nil? && i.agent.agent_id == event.agent.agent_id
326
- # i.status = Service::STATUS_FAIL
327
- # end
328
- # }
329
-
330
- # }
312
+ end
313
+
314
+ class TriggerSet
315
+ def initialize(service_cluster_id)
316
+ @service_cluster_id = service_cluster_id
317
+ @triggers = []
331
318
  end
332
- thread_immutable_methods :prepare
333
319
 
320
+ def add_trigger(trigger)
321
+ raise ArguemntError unless trigger.is_a?(Trigger)
322
+ @triggers << trigger
323
+ Wakame.log.debug(trigger.inspect)
324
+ trigger.register_hooks(@service_cluster_id)
325
+ end
326
+ alias :register_trigger :add_trigger
327
+ end
334
328
 
335
- def update_cluster_status
336
- onlines = []
337
- all_offline = false
338
- onlines = self.instances.select { |k, i|
339
- i.status == Service::STATUS_ONLINE
329
+
330
+ class ServiceCluster < StatusDB::Model
331
+ include ThreadImmutable
332
+
333
+ STATUS_OFFLINE = 0
334
+ STATUS_ONLINE = 1
335
+ STATUS_PARTIAL_ONLINE = 2
336
+
337
+ property :name
338
+ property :status, {:readonly=>true, :default=>STATUS_OFFLINE}
339
+ property :status_changed_at, {:readonly=>true, :default=>proc{Time.now} }
340
+ property :services, {:default=>{}}
341
+ property :resources, {:default=>{}}
342
+ property :cloud_hosts, {:default=>{}}
343
+ property :dg_id
344
+ property :template_vm_attr, {:default=>{}}
345
+ property :advertised_amqp_servers
346
+
347
+ attr_reader :trigger_set
348
+
349
+ def self.id(name)
350
+ require 'digest/sha1'
351
+ Digest::SHA1.hexdigest(name)
352
+ end
353
+
354
+ def id
355
+ raise "Cluster name is not set yes" if self.name.nil?
356
+ self.class.id(self.name)
357
+ end
358
+
359
+ def define_triggers(&blk)
360
+ @trigger_set ||= TriggerSet.new(self.id)
361
+ blk.call(@trigger_set)
362
+ end
363
+
364
+ def template_vm_spec
365
+ spec = VmSpec.current
366
+ spec.table = self.template_vm_attr
367
+ spec
368
+ end
369
+
370
+ def reset
371
+ services.clear
372
+ resources.clear
373
+ cloud_hosts.clear
374
+ template_vm_attr.clear
375
+ advertised_amqp_servers = nil
376
+ @status = self.class.attr_attributes[:status][:default]
377
+ @status_changed_at = Time.now
378
+ end
379
+
380
+ def mapped_agent?(agent_id)
381
+ cloud_hosts.keys.any? { |cloud_host_id|
382
+ h = CloudHost.find(cloud_host_id)
383
+ h.mapped? && h.agent_id == agent_id
340
384
  }
341
- all_offline = self.instances.all? { |k, i|
342
- i.status == Service::STATUS_OFFLINE
385
+ end
386
+
387
+ def agents
388
+ res={}
389
+ cloud_hosts.keys.collect { |cloud_host_id|
390
+ h = CloudHost.find(cloud_host_id)
391
+ res[cloud_host_id]=h.agent_id if h.mapped?
343
392
  }
344
- #Wakame.log.debug "online instances: #{onlines.size}, assigned instances: #{self.instances.size}"
345
- if self.instances.size == 0 || all_offline
346
- self.status = Service::ServiceCluster::STATUS_OFFLINE
347
- elsif onlines.size == self.instances.size
348
- self.status = Service::ServiceCluster::STATUS_ONLINE
349
- elsif onlines.size > 0
350
- self.status = Service::ServiceCluster::STATUS_PARTIAL_ONLINE
393
+ res
394
+ end
395
+
396
+ def dg
397
+ unless self.dg_id.nil?
398
+ graph = DependencyGraph.find(self.dg_id)
399
+ else
400
+ graph = DependencyGraph.new
401
+ graph.save
402
+ self.dg_id = graph.id
403
+ self.save
351
404
  end
352
-
405
+ graph
353
406
  end
354
- thread_immutable_methods :update_cluster_status
355
-
356
- end
357
407
 
358
- class LockQueue
359
- def initialize(cluster)
360
- @service_cluster = cluster
361
- @locks = {}
362
- @id2res = {}
408
+ #
409
+ def add_resource(resource, &blk)
410
+ if resource.is_a?(Class) && resource <= Resource
411
+ resource = resource.new
412
+ elsif resource.is_a? Resource
413
+ else
414
+ raise ArgumentError
415
+ end
416
+ raise "Duplicate resource registration: #{resource.class}" if self.resources.has_key? resource.id
417
+
418
+ blk.call(resource) if blk
363
419
 
364
- @queue_by_thread = {}
365
- @qbt_m = ::Mutex.new
420
+ resources[resource.id]=1
421
+ self.dg.add_object(resource)
422
+
423
+ resource.save
424
+ self.save
425
+ self.dg.save
426
+ resource
366
427
  end
367
-
368
- def set(resource, id)
369
- # Ths Job ID already holds/reserves the lock regarding the resource.
370
- return if @id2res.has_key?(id) && @id2res[id].has_key?(resource.to_s)
428
+ thread_immutable_methods :add_resource
371
429
 
372
- EM.barrier {
373
- @locks[resource.to_s] ||= []
374
- @id2res[id] ||= {}
375
-
376
- @id2res[id][resource.to_s]=1
377
- @locks[resource.to_s] << id
430
+ # Set dependency between two resources.
431
+ def set_dependency(res_name1, res_name2)
432
+ validate_arg = proc {|o|
433
+ o = Util.build_const(o) if o.is_a? String
434
+ raise ArgumentError unless o.is_a?(Class) && o <= Resource
435
+ raise "This is not a member of this cluster \"#{self.class}\": #{o}" unless resources.member?(o.id)
436
+ raise "Unknown resource object: #{o}" unless Resource.exists?(o.id)
437
+ o
378
438
  }
379
- Wakame.log.debug("#{self.class}: set(#{resource.to_s}, #{id})" + "\n#{self.inspect}")
439
+
440
+ res_name1 = validate_arg.call(res_name1)
441
+ res_name2 = validate_arg.call(res_name2)
442
+
443
+ return if res_name1.id == res_name2.id
444
+
445
+ self.dg.set_dependency(res_name1, res_name2)
380
446
  end
447
+ thread_immutable_methods :set_dependency
381
448
 
382
- def reset()
383
- @locks.keys { |k|
384
- @locks[k].clear
385
- }
386
- @id2res.clear
449
+ def has_instance?(svc_id)
450
+ self.services.has_key? svc_id
387
451
  end
388
452
 
389
- def test(id)
390
- reslist = @id2res[id]
391
- return :pass if reslist.nil? || reslist.empty?
453
+ def shutdown
454
+ end
455
+ thread_immutable_methods :shutdown
456
+
457
+ # Create service instance objects which will be equivalent with the number min_instance.
458
+ # The agents are not assigned at this point.
459
+ #def launch
460
+ # self.resources.keys.each { |res_id|
461
+ # res = Resource.find(res_id)
462
+ # count = instance_count(res.class)
463
+ # if res.min_instances > count
464
+ # (res.min_instances - count).times {
465
+ # propagate(res.class)
466
+ # }
467
+ # end
468
+ # }
469
+ #end
470
+ #thread_immutable_methods :launch
471
+
472
+ def destroy(svc_id)
473
+ raise("Unknown service instance : #{svc_id}") unless self.services.has_key?(svc_id)
474
+ svc = ServiceInstance.find(svc_id)
475
+ svc.unbind_cluster
476
+ self.services.delete(svc.id)
477
+ old_host = svc.unbind_cloud_host
392
478
 
393
- #
394
- if reslist.keys.all? { |r| id == @locks[r.to_s][0] }
395
- return :runnable
479
+ if old_host
480
+ Wakame.log.debug("#{svc.resource.class}(#{svc.id}) has been destroied from Host #{old_host.inspect}")
396
481
  else
397
- return :wait
482
+ Wakame.log.debug("#{svc.resource.class}(#{svc.id}) has been destroied.")
398
483
  end
484
+
485
+ svc.delete
486
+ self.save
399
487
  end
488
+ thread_immutable_methods :destroy
489
+
400
490
 
401
- def wait(id, tout=60*30)
402
- @qbt_m.synchronize { @queue_by_thread[Thread.current] = ::Queue.new }
491
+ #def propagate(resource, force=false)
492
+ def propagate_resource(resource, cloud_host_id=nil, force=false)
493
+ res_id = Resource.id(resource)
494
+ res_obj = (self.resources.has_key?(res_id) && Resource.find(res_id)) || raise("Unregistered resource: #{resource.to_s}")
495
+ raise ArgumentError if res_obj.require_agent && cloud_host_id.nil?
403
496
 
404
- timeout(tout) {
405
- while test(id) == :wait
406
- Wakame.log.debug("#{self.class}: Job #{id} waits for locked resouces: #{@id2res[id].keys.join(', ')}")
407
- break if id == @queue_by_thread[Thread.current].deq
497
+ if force == false
498
+ instnum = instance_count(res_obj)
499
+ if instnum >= res_obj.max_instances
500
+ raise ServicePropagationError, "#{res_obj.class} has been reached to max_instance limit: max=#{res_obj.max_instances}"
408
501
  end
409
- }
410
- ensure
411
- @qbt_m.synchronize { @queue_by_thread.delete(Thread.current) }
502
+ end
503
+
504
+ svc = ServiceInstance.new
505
+ svc.bind_cluster(self)
506
+ svc.bind_resource(res_obj)
507
+
508
+ # cloud_host_id must be set when the resource is placed on agent.
509
+ if res_obj.require_agent
510
+ host = CloudHost.find(cloud_host_id) || raise("#{self.class}: Unknown CloudHost ID: #{cloud_host_id}")
511
+ svc.bind_cloud_host(host)
512
+ end
513
+
514
+ self.services[svc.id]=1
515
+
516
+ svc.save
517
+ self.save
518
+
519
+ svc
412
520
  end
413
-
414
- def quit(id)
415
- EM.barrier {
416
- case test(id)
417
- when :runnable, :wait
418
- @id2res[id].keys.each { |r| @locks[r.to_s].delete_if{ |i| i == id } }
419
- @qbt_m.synchronize {
420
- @queue_by_thread.each {|t, q| q.enq(id) }
521
+ thread_immutable_methods :propagate_resource
522
+ alias :propagate :propagate_resource
523
+
524
+ def propagate_service(svc_id, cloud_host_id=nil, force=false)
525
+ src_svc = (self.services.has_key?(svc_id) && ServiceInstance.find(svc_id)) || raise("Unregistered service: #{svc_id.to_s}")
526
+ res_obj = src_svc.resource
527
+
528
+ if force == false
529
+ instnum = instance_count(res_obj)
530
+ if instnum >= res_obj.max_instances
531
+ raise ServicePropagationError, "#{res_obj.class} has been reached to max_instance limit: max=#{res_obj.max_instances}"
532
+ end
533
+ end
534
+
535
+ svc = ServiceInstance.new
536
+ svc.bind_cluster(self)
537
+ svc.bind_resource(res_obj)
538
+
539
+ if res_obj.require_agent
540
+ if cloud_host_id
541
+ host = CloudHost.find(cloud_host_id) || raise("#{self.class}: Unknown Host ID: #{cloud_host_id}")
542
+ else
543
+ host = add_cloud_host { |h|
544
+ h.vm_attr = src_svc.cloud_host.vm_attr.dup
421
545
  }
422
546
  end
423
-
424
- @id2res.delete(id)
547
+ svc.bind_cloud_host(host)
548
+ end
549
+
550
+ self.services[svc.id]=1
551
+
552
+ svc.save
553
+ self.save
554
+
555
+ svc
556
+ end
557
+ thread_immutable_methods :propagate_service
558
+
559
+ def add_cloud_host(&blk)
560
+ h = CloudHost.new
561
+ h.cluster_id = self.id
562
+ self.cloud_hosts[h.id]=1
563
+
564
+ blk.call(h) if blk
565
+
566
+ h.save
567
+ self.save
568
+
569
+ h
570
+ end
571
+
572
+ def remove_cloud_host(cloud_host_id)
573
+ if self.cloud_hosts.has_key?(cloud_host_id)
574
+ self.cloud_hosts.delete(cloud_host_id)
575
+
576
+ self.save
577
+ end
578
+
579
+ CloudHost.delete(cloud_host_id) rescue nil
580
+ end
581
+
582
+ def find_service(svc_id)
583
+ raise "The service ID #{svc_id} is not registered to this cluster \"#{self.name}\"" unless self.services.has_key? svc_id
584
+ ServiceInstance.find(svc_id) || raise("The service ID #{svc_id} is registered. but not in the database.")
585
+ end
586
+
587
+ def instance_count(resource=nil)
588
+ return self.services.size if resource.nil?
589
+
590
+ c = 0
591
+ each_instance(resource) { |svc|
592
+ c += 1
425
593
  }
426
- Wakame.log.debug("#{self.class}: quit(#{id})" + "\n#{self.inspect}")
594
+ c
595
+ end
596
+
597
+ # Iterate the service instances in this cluster
598
+ #
599
+ # The first argument is used for filtering only specified resource instances.
600
+ # Iterated instance objects are passed to the block when it is given. The return value is an array contanins registered service instance objects (filtered).
601
+ def each_instance(filter_resource=nil, &blk)
602
+ filter_resource = case filter_resource
603
+ when Resource
604
+ filter_resource.class
605
+ when String
606
+ Util.build_const(filter_resource)
607
+ when Module, NilClass
608
+ filter_resource
609
+ else
610
+ raise ArgumentError, "The first argument has to be in form of NilClass, Resource, String or Module: #{filter_resource.class}"
611
+ end
612
+
613
+ filter_ids = []
614
+
615
+ unless filter_resource.nil?
616
+ filter_ids = self.resources.keys.find_all { |resid|
617
+ Resource.find(resid).kind_of?(filter_resource)
618
+ }
619
+ return [] if filter_ids.empty?
620
+ end
621
+
622
+ ary = self.services.keys.collect {|k| ServiceInstance.find(k) }
623
+ if filter_resource.nil?
624
+ else
625
+ ary = ary.find_all{|v| filter_ids.member?(v.resource.id) }
626
+ end
627
+
628
+ ary.each {|v| blk.call(v) } if block_given?
629
+ ary
427
630
  end
428
631
 
429
- def clear_resource(resource)
632
+ def update_status(new_status)
633
+ if @status != new_status
634
+ @status = new_status
635
+ @status_changed_at = Time.now
636
+
637
+ self.save
638
+
639
+ ED.fire_event(Event::ClusterStatusChanged.new(id, new_status))
640
+ end
641
+ end
642
+ thread_immutable_methods :update_status
643
+
644
+ def size
645
+ self.dg.size
646
+ end
647
+
648
+ def properties
649
+ self.resources
430
650
  end
431
651
 
432
- def inspect
433
- output = @locks.collect { |k, lst|
434
- [k, lst].flatten
652
+ alias :instances :services
653
+
654
+ #private
655
+
656
+ def update_cluster_status
657
+ onlines = []
658
+ all_offline = false
659
+
660
+ onlines = self.each_instance.select { |i|
661
+ i.status == Service::STATUS_ONLINE
435
662
  }
436
- return "" if output.empty?
437
-
438
- # Table display
439
- maxcolws = (0..(output.size)).zip(*output).collect { |i| i.shift; i.map!{|i| (i.nil? ? "" : i).length }.max }
440
- maxcol = maxcolws.size
441
- maxcolws.reverse.each { |i|
442
- break if i > 0
443
- maxcol -= 1
663
+ all_offline = self.each_instance.all? { |i|
664
+ i.status == Service::STATUS_OFFLINE
444
665
  }
666
+ #Wakame.log.debug "online instances: #{onlines.size}, assigned instances: #{self.instances.size}"
445
667
 
446
- textrows = output.collect { |x|
447
- buf=""
448
- maxcol.times { |n|
449
- buf << "|" + (x[n] || "").ljust(maxcolws[n])
450
- }
451
- buf << "|"
452
- }
668
+ prev_status = self.status
669
+ if self.instances.size == 0 || all_offline
670
+ self.update_status(Service::ServiceCluster::STATUS_OFFLINE)
671
+ elsif onlines.size == self.instances.size
672
+ self.update_status(Service::ServiceCluster::STATUS_ONLINE)
673
+ elsif onlines.size > 0
674
+ self.update_status(Service::ServiceCluster::STATUS_PARTIAL_ONLINE)
675
+ end
453
676
 
454
- "+" + (["-"] * (textrows[0].length - 2)).join('') + "+\n" + \
455
- textrows.join("\n") + \
456
- "\n+" + (["-"] * (textrows[0].length - 2)).join('')+ "+"
457
677
  end
678
+ thread_immutable_methods :update_cluster_status
679
+
458
680
  end
681
+
459
682
 
460
-
461
- class DependencyGraph
683
+ class DependencyGraph < StatusDB::Model
462
684
 
463
- def initialize(service_cluster)
685
+ property :nodes, {:default=>{}}
686
+ property :graph_edges
687
+
688
+ def initialize()
464
689
  @graph = Graph.new
690
+ @graph.edges = self.graph_edges = {}
465
691
  @graph.add_vertex(0)
466
- @service_cluster = service_cluster
467
- @nodes = {}
468
692
  end
469
693
 
470
-
471
694
  def add_object(obj)
472
- @nodes[obj.hash] = obj
473
- @graph.add_edge(0, obj.hash)
695
+ res_id = Resource.id(obj)
696
+ self.nodes[res_id.hash] = res_id
697
+ @graph.add_edge(0, res_id.hash)
698
+ self.save
474
699
  self
475
700
  end
476
701
 
477
- def set_dependency(parent_obj, child_obj)
478
- return if parent_obj == child_obj
479
- @graph.add_edge(parent_obj.hash, child_obj.hash)
480
- @graph.remove_edge(0, child_obj.hash) if @graph.has_edge?(0, child_obj.hash)
702
+ def set_dependency(parent_res, child_res)
703
+ p_res_id = Resource.id(parent_res)
704
+ c_res_id = Resource.id(child_res)
705
+ return if p_res_id == c_res_id
706
+
707
+ self.nodes[p_res_id.hash]=p_res_id
708
+ self.nodes[c_res_id.hash]=c_res_id
709
+
710
+ @graph.add_edge(p_res_id.hash, c_res_id.hash)
711
+ @graph.remove_edge(0, c_res_id.hash) if @graph.has_edge?(0, c_res_id.hash)
712
+ self.save
481
713
  self
482
714
  end
483
-
715
+
716
+ #
484
717
  def size
485
718
  @graph.size - 1
486
719
  end
487
720
 
488
- def parents(obj)
489
- obj = case obj
490
- when Class
491
- obj.to_s.hash
492
- when String
493
- obj.hash
494
- else
495
- raise ArgumentError
496
- end
497
- @graph.parents(obj).collect { |hashid| property_obj(hashid) }
498
- end
499
-
500
- def children(obj)
501
- obj = case obj
502
- when Class
503
- obj.to_s.hash
504
- when String
505
- obj.hash
506
- else
507
- raise ArgumentError
508
- end
509
- @graph.children(obj).collect { |hashid| property_obj(hashid) }
721
+ # Returns an array with the parent resources of given node
722
+ def parents(res)
723
+ # delete() returns nil when nothing was removed. so use delete_if instead.
724
+ @graph.parents(Resource.id(res).hash).delete_if{|i| i == 0}.collect { |hashid| id2obj(hashid) }
725
+ end
726
+
727
+ # Returns an array with the child resources of given node
728
+ def children(res)
729
+ # delete() returns nil when nothing was removed. so use delete_if instead.
730
+ @graph.children(Resource.id(res).hash).delete_if{|i| i == 0}.collect { |hashid| id2obj(hashid) }
510
731
  end
511
732
 
512
733
  def levels(root=nil)
513
- root = case root
514
- when nil
515
- 0
516
- when Class
517
- root.to_s.hash
518
- when String
519
- root.hash
520
- else
521
- raise ArgumentError
522
- end
734
+ root = root.nil? ? 0 : Resource.id(root).hash
735
+
523
736
  n=[]
524
737
  @graph.level_layout(root).each { |l|
525
738
  next if l.size == 1 && l[0] == 0
526
- n << l.collect { |hashid| property_obj(hashid)}
527
- #n << l.collect { |hashid| @nodes[hashid].to_s }
739
+ n << l.collect { |hashid| id2obj(hashid) }
528
740
  }
529
741
  n
530
742
  end
531
743
 
532
744
  def each_level(root=nil, &blk)
533
- root = case root
534
- when nil
535
- 0
536
- when Class
537
- root.to_s.hash
538
- when String
539
- root.hash
540
- else
541
- raise ArgumentError
542
- end
745
+ root = root.nil? ? 0 : Resource.id(root).hash
746
+
543
747
  @graph.level_layout(root).each { |l|
544
748
  l.each { |hashid|
545
749
  next if hashid == 0
546
- blk.call(@service_cluster.properties[@nodes[hashid]])
750
+ blk.call(id2obj(hashid))
547
751
  }
548
752
  }
549
753
  end
754
+
755
+ def on_after_load
756
+ # Delegate the edge data to be handled by Graph class when it is loaded from database.
757
+ @graph.edges = self.graph_edges
758
+ end
550
759
 
551
760
  private
552
- def property_obj(hashid)
553
- @service_cluster.properties[@nodes[hashid]]
761
+ def id2obj(hashid)
762
+ Resource.find(self.nodes[hashid])
554
763
  end
555
764
  end
556
765
 
557
-
558
- class ServiceInstance
766
+
767
+ # The data model represents a service instance.
768
+ # Status transition:
769
+ # STATUS_INIT -> [STATUS_OFFLINE|STATUS_ONLINE|STATUS_FAIL] -> STATUS_END
770
+ # Progress status:
771
+ # STATUS_NONE, STATUS_MIGRATING, STATUS_PROPAGATING,
772
+ class ServiceInstance < StatusDB::Model
559
773
  include ThreadImmutable
560
- attr_reader :instance_id, :service_property, :agent, :service_cluster, :status_changed_at
561
- attr_accessor :name
562
- alias :cluster :service_cluster
563
- alias :property :service_property
564
-
565
- class << self
566
- def instance_collection
567
- @collection ||= {}
568
- end
569
- end
570
-
571
- def initialize(service_property)
572
- bind_thread
573
- raise TypeError unless service_property.is_a?(Property)
574
-
575
- @instance_id = Wakame.gen_id
576
- @service_property = service_property
577
- @status = Service::STATUS_OFFLINE
578
- @status_changed_at = Time.now
579
774
 
580
- self.class.instance_collection[@instance_id] = self
581
- end
582
-
775
+ property :cloud_host_id
776
+ property :resource_id
777
+ property :cluster_id
778
+ property :status, {:read_only=>true, :default=>Service::STATUS_INIT}
779
+ property :monitor_status, {:default=>Service::STATUS_INIT}
780
+ property :status_changed_at, {:read_only=>true, :default=>proc{Time.now}}
781
+
583
782
  def update_status(new_status, changed_at=Time.now, fail_message=nil)
584
783
  if @status != new_status
585
784
  prev_status = @status
586
785
  @status = new_status
587
786
  @status_changed_at = changed_at
588
787
 
589
- event = Event::ServiceStatusChanged.new(@instance_id, @service_property, new_status, prev_status)
788
+ self.save
789
+
790
+ event = Event::ServiceStatusChanged.new(self.id, resource, new_status, prev_status)
590
791
  event.time = @status_changed_at.dup
591
792
  ED.fire_event(event)
592
793
 
593
794
  tmp_event = nil
594
795
  if prev_status != Service::STATUS_ONLINE && new_status == Service::STATUS_ONLINE
595
- tmp_event = Event::ServiceOnline.new(self.instance_id, self.property)
796
+ tmp_event = Event::ServiceOnline.new(self.id, self.resource)
596
797
  tmp_event.time = @status_changed_at.dup
597
798
  elsif prev_status != Service::STATUS_OFFLINE && new_status == Service::STATUS_OFFLINE
598
- tmp_event = Event::ServiceOffline.new(self.instance_id, self.property)
799
+ tmp_event = Event::ServiceOffline.new(self.id, self.resource)
599
800
  tmp_event.time = @status_changed_at.dup
600
801
  elsif prev_status != Service::STATUS_FAIL && new_status == Service::STATUS_FAIL
601
- tmp_event = Event::ServiceFailed.new(self.instance_id, self.property, fail_message)
802
+ tmp_event = Event::ServiceFailed.new(self.id, self.resource, fail_message)
602
803
  tmp_event.time = @status_changed_at.dup
603
804
  end
604
805
  ED.fire_event(tmp_event) if tmp_event
605
806
 
606
807
  end
607
- @status
608
808
  end
609
- thread_immutable_methods :update_status
610
809
 
810
+ def cloud_host
811
+ raise "The associated resource is not needed to have agent." unless self.resource.require_agent
812
+
813
+ self.cloud_host_id.nil? ? nil : CloudHost.find(self.cloud_host_id)
814
+ end
611
815
 
612
- def status
613
- @status
816
+ def bind_cloud_host(new_cloud_host)
817
+ return if !self.resource.require_agent || self.cloud_host_id == new_cloud_host.id
818
+ raise "The host (#{new_cloud_host.id}) was assigned same service already: #{resource.class}" if new_cloud_host.has_resource_type?(resource)
819
+
820
+ unbind_cloud_host
821
+
822
+ self.cloud_host_id = new_cloud_host.id
823
+ self.save
824
+
825
+ ED.fire_event(Event::ServiceBoundHost.new(self, new_cloud_host))
614
826
  end
827
+ thread_immutable_methods :bind_cloud_host
615
828
 
616
- def property
617
- @service_property
829
+ def unbind_cloud_host
830
+ return if self.cloud_host_id.nil?
831
+
832
+ old_item = self.cloud_host
833
+ self.cloud_host_id = nil
834
+
835
+ self.save
836
+
837
+ ED.fire_event(Event::ServiceUnboundHost.new(self, old_item))
838
+ old_item
618
839
  end
840
+ thread_immutable_methods :unbind_cloud_host
841
+
619
842
  def resource
620
- @service_property
621
- end
622
-
623
- def type
624
- @service_property.class
843
+ return nil if self.resource_id.nil?
844
+ Resource.find(self.resource_id)
625
845
  end
626
846
 
627
- def bind_agent(agent)
628
- return if agent.nil? || (@agent && agent.agent_id == @agent.agent_id)
629
- raise "The agent (#{agent.agent_id}) was assigned same service already: #{property.class}" if agent.has_service_type?(property.class)
630
-
631
- # UboundAgent & BoundAgent event occured only when the different agent obejct is assigned.
632
- unbind_agent
633
- @agent = agent
634
- @agent.services[instance_id]=self
635
-
636
- ED.fire_event(Event::ServiceBoundAgent.new(self, agent))
637
- @agent
847
+ def bind_resource(resource)
848
+ return if self.resource_id == resource.id
849
+
850
+ unbind_resource
851
+ self.resource_id = resource.id
852
+ self.save
853
+
854
+ #ED.fire_event(Event::ServiceBoundCluster.new(self, cluster))
638
855
  end
639
- thread_immutable_methods :bind_agent
640
-
641
- def unbind_agent
642
- return nil if @agent.nil?
643
- @agent.services.delete(instance_id)
644
- old_item = @agent
645
- @agent = nil
646
- ED.fire_event(Event::ServiceUnboundAgent.new(self, old_item))
856
+ thread_immutable_methods :bind_resource
857
+
858
+ def unbind_resource
859
+ return if self.resource_id.nil?
860
+ old_item = self.resource_id
861
+
862
+ self.resource_id = nil
863
+ self.save
864
+
865
+ #ED.fire_event(Event::ServiceUnboundCluster.new(self, old_item))
647
866
  old_item
648
867
  end
649
- thread_immutable_methods :unbind_agent
868
+ thread_immutable_methods :unbind_resource
869
+
870
+
871
+ def cluster
872
+ return nil if self.cluster_id.nil?
873
+ ServiceCluster.find(self.cluster_id)
874
+ end
650
875
 
651
876
  def bind_cluster(cluster)
652
- return if cluster.nil? || (@service_cluster && cluster.instance_id == @service_cluster.instance_id)
877
+ return if self.cluster_id == cluster.id
878
+
653
879
  unbind_cluster
654
- @service_cluster = cluster
880
+ self.cluster_id = cluster.id
881
+ self.save
882
+
655
883
  ED.fire_event(Event::ServiceBoundCluster.new(self, cluster))
656
884
  end
657
885
  thread_immutable_methods :bind_cluster
658
886
 
659
887
  def unbind_cluster
660
- return if @service_cluster.nil?
661
- old_item = @service_cluster
662
- @service_cluster = nil
888
+ return if self.cluster_id.nil?
889
+ old_item = self.cluster_id
890
+
891
+ self.cluster_id = nil
892
+ self.save
893
+
663
894
  ED.fire_event(Event::ServiceUnboundCluster.new(self, old_item))
895
+ old_item
664
896
  end
665
897
  thread_immutable_methods :unbind_cluster
666
898
 
@@ -668,17 +900,10 @@ module Wakame
668
900
  binding
669
901
  end
670
902
 
671
- def dump_status
672
- ret = {:type => self.class.to_s, :status => status, :property => property.class.to_s, :instance_id => instance_id}
673
- ret[:agent_id] = agent.agent_id if agent
674
- ret
675
- end
676
- thread_immutable_methods :dump_status
677
-
678
903
  def parent_instances
679
904
  ary = []
680
- @service_cluster.dg.parents(resource.class).each { |r|
681
- @service_cluster.each_instance(r.class){ |i|
905
+ self.cluster.dg.parents(resource.class).each { |r|
906
+ self.cluster.each_instance(r.class){ |i|
682
907
  ary << i
683
908
  }
684
909
  }
@@ -687,8 +912,8 @@ module Wakame
687
912
 
688
913
  def child_instances
689
914
  ary = []
690
- @service_cluster.dg.children(resource.class).each { |r|
691
- @service_cluster.each_instance(r.class){ |i|
915
+ self.cluster.dg.children(resource.class).each { |r|
916
+ self.cluster.each_instance(r.class){ |i|
692
917
  ary << i
693
918
  }
694
919
  }
@@ -697,78 +922,119 @@ module Wakame
697
922
  end
698
923
 
699
924
 
700
- class VmSpec
701
- def self.define(&blk)
702
- spec = self.new
703
- spec.instance_eval(&blk)
704
- spec
705
- end
706
-
707
- def initialize
708
- @environments = {}
709
- end
710
-
711
- def current
925
+ class VmSpec < OpenStruct
926
+ def self.current
712
927
  environment(Wakame.config.environment)
713
928
  end
714
929
 
715
- def environment(klass_key, &blk)
716
- envobj = @environments[klass_key]
717
- if envobj.nil?
930
+ def self.environment(klass_key)
931
+ @templates ||= {}
932
+
933
+ tmpl_klass = @templates[klass_key]
934
+ if tmpl_klass.nil?
718
935
  #klass = self.class.constants.find{ |c| c.to_s == klass_key.to_s }
719
- if self.class.const_defined?(klass_key)
720
- envobj = @environments[klass_key] = Wakame.new_([self.class.to_s, klass_key.to_s].join('::'))
936
+ if self.const_defined?(klass_key)
937
+ tmpl_klass = @templates[klass_key] = Util.build_const([self.to_s, klass_key.to_s].join('::'))
721
938
  else
722
- raise "Undefined VM Spec template : #{klass_key}"
939
+ raise "Undefined VM Spec Template : #{klass_key}"
723
940
  end
724
941
  end
725
942
 
726
- envobj.instance_eval(&blk) if blk
943
+ self.new(tmpl_klass.new)
944
+ end
945
+
727
946
 
728
- envobj
947
+ def initialize(template, vm_attr=nil)
948
+ @template = template
949
+ if vm_attr.is_a? Hash
950
+ @table = vm_attr
951
+ else
952
+ h = {}
953
+ @template.class.vm_attr_defs.keys.each {|k| h[k]=nil }
954
+ super(h)
955
+ end
956
+ end
957
+ protected :initialize
958
+
959
+ def table=(vm_attr)
960
+ @table = vm_attr
961
+ end
962
+
963
+ def attrs
964
+ table
965
+ end
966
+
967
+ def satisfy?(vm_attr)
968
+ @template.satisfy?(vm_attr, table)
729
969
  end
970
+
971
+ def merge(src_vm_attr)
972
+ @template.merge(src_vm_attr, table)
973
+ end
974
+
975
+
730
976
 
731
977
  class Template
732
978
  def self.inherited(klass)
733
979
  klass.class_eval {
734
- def self.default_attr_values
735
- @default_attr_values ||= {}
980
+ def self.vm_attr_defs
981
+ @vm_attr_defs ||= {}
736
982
  end
737
- def self.def_attribute(name, default_value=nil)
738
- default_attr_values[name.to_sym]= default_value
739
- attr_accessor(name)
983
+
984
+ def self.vm_attr(key, opts=nil)
985
+ opts ||= {}
986
+
987
+ vm_attr_defs[key.to_sym]=opts
740
988
  end
741
989
  }
742
- end
743
-
744
- def initialize
745
- @attribute_keys=[]
746
- self.class.default_attr_values.each { |n, v|
747
- instance_variable_set("@#{n.to_s}", v)
748
- #self.instance_eval %Q{ #{n} = #{v} }
749
- @attribute_keys << n
750
- }
990
+
751
991
  end
752
992
 
753
- def attrs
754
- a={}
755
- @attribute_keys.each { |k|
756
- a[k.to_sym]=instance_variable_get("@#{k.to_s}")
757
- }
758
- a
993
+ def satisfy?(vm_attr, diff)
994
+ raise ""
759
995
  end
760
996
 
761
- def satisfy?(agent)
762
- true
997
+ def merge(src_vm_attr, diff)
998
+ raise ""
763
999
  end
1000
+
764
1001
  end
765
1002
 
766
1003
  class EC2 < Template
767
1004
  AWS_VERSION=''
768
- def_attribute :instance_type, 'm1.small'
769
- def_attribute :availability_zone
770
- def_attribute :key_name
771
- def_attribute :security_groups, []
1005
+ vm_attr :instance_type, {:choice=>%w[m1.small m1.large m1.xlarge c1.medium c1.xlarge], :right_aws_key=>:aws_instance_type}
1006
+ vm_attr :availability_zone, {:right_aws_key=>:aws_availability_zone}
1007
+ vm_attr :key_name, {:right_aws_key=>:ssh_key_name}
1008
+ vm_attr :security_groups, {:default=>['default'], :right_aws_key=>:aws_groups}
1009
+ vm_attr :image_id, {:right_aws_key=>:aws_image_id}
1010
+
1011
+
1012
+ def satisfy?(vm_attr, diff)
1013
+ # Compare critical variables which will return false if they are not same.
1014
+ return false unless [:availability_zone, :instance_type, :image_id].all? { |k| diff[k].nil? ? true : diff[k] == vm_attr[k] }
1015
+ true
1016
+ end
1017
+
1018
+ def merge(vm_attr, diff)
1019
+ self.class.vm_attr_defs.each_key { |k|
1020
+ raise "Passed VM attribute hash is incomplete data set: #{vm_attr}" unless vm_attr.has_key? k
1021
+ }
1022
+
1023
+ merged = vm_attr.merge(diff){ |k,v1,v2|
1024
+ case k
1025
+ when :security_groups
1026
+ if v1.is_a?(Array)
1027
+ (v1.dup << v2).flatten.uniq
1028
+ else
1029
+ v2
1030
+ end
1031
+ else
1032
+ v2.nil? ? v1 : v2
1033
+ end
1034
+ }
1035
+
1036
+ merged
1037
+ end
772
1038
  end
773
1039
 
774
1040
  class StandAlone < Template
@@ -776,37 +1042,54 @@ module Wakame
776
1042
  end
777
1043
 
778
1044
 
779
- class Property
780
- include AttributeHelper
781
- attr_accessor :check_time, :vm_spec
782
- def_attribute :duplicable, true
783
- def_attribute :min_instances, 1
784
- def_attribute :max_instances, 1
785
- def_attribute :startup, true
786
- def_attribute :require_agent, true
787
-
788
- def initialize(check_time=5)
789
- @check_time = check_time
790
- @vm_spec = VmSpec.define {
791
- environment(:EC2) { |ec2|
792
- ec2.instance_type = 'm1.small'
793
- ec2.availability_zone = 'us-east-1c'
794
- ec2.security_groups = ['default']
795
- }
796
-
797
- environment(:StandAlone) {
798
- }
1045
+ class Resource < StatusDB::Model
1046
+ property :duplicable, {:default=>true}
1047
+ property :min_instances, {:default=>1}
1048
+ property :max_instances, {:default=>1}
1049
+ property :startup, {:default=>true}
1050
+ property :require_agent, {:default=>true}
1051
+
1052
+ def self.inherited(klass)
1053
+ klass.class_eval {
1054
+ def self.id
1055
+ Resource.id(self)
1056
+ end
799
1057
  }
800
1058
  end
801
1059
 
802
- def basedir
803
- File.join(Wakame.config.root_path, 'cluster', 'resources', Util.snake_case(self.class))
1060
+ def self.name(id)
1061
+ find(id).class.to_s
1062
+ end
1063
+
1064
+ # Returns the hashed resource class name representation.
1065
+ # Resource.id() is as same as sha1.digest('Wakame::Service::Resource')
1066
+ # With an argument, tries to get the class name and take digest.
1067
+ def self.id(name=nil)
1068
+ res_class_name = case name
1069
+ when nil
1070
+ self.to_s
1071
+ when String
1072
+ raise "Invalid string as ruby constant: #{name}" unless name =~ /^(:-\:\:)?[A-Z]/
1073
+ name.to_s
1074
+ when Class
1075
+ raise "Can't convert the argument: type of #{name.class}" unless name <= self
1076
+ name.to_s
1077
+ when Resource
1078
+ name.class.to_s
1079
+ else
1080
+ raise "Can't convert the argument: type of #{name.class}"
1081
+ end
1082
+
1083
+ require 'digest/sha1'
1084
+ Digest::SHA1.hexdigest(res_class_name)
1085
+ end
1086
+
1087
+ def id
1088
+ Resource.id(self.class.to_s)
804
1089
  end
805
1090
 
806
- def dump_status
807
- {:type => self.class.to_s, :min_instances => min_instances, :max_instances=> max_instances,
808
- :duplicable=>duplicable
809
- }
1091
+ def basedir
1092
+ File.join(Wakame.config.root_path, 'cluster', 'resources', Util.snake_case(self.class))
810
1093
  end
811
1094
 
812
1095
  def start(service_instance, action); end
@@ -816,30 +1099,26 @@ module Wakame
816
1099
  def render_config(template)
817
1100
  end
818
1101
 
819
- #def before_start(service_instance, action)
820
- #end
821
- #def after_start(service_instance, action)
822
- #end
823
- #def before_stop(service_instance, action)
824
- #end
825
- #def after_stop(service_instance, action)
826
- #end
827
-
828
1102
  def on_child_changed(service_instance, action)
829
1103
  end
830
1104
  def on_parent_changed(service_instance, action)
831
1105
  end
1106
+
1107
+ def on_enter_agent(service_instance, action)
1108
+ end
1109
+ def on_quit_agent(service_instance, action)
1110
+ end
832
1111
 
833
1112
  end
834
1113
 
835
- Resource = Property
1114
+ Property = Resource
836
1115
  end
837
1116
  end
838
1117
 
839
1118
  module Wakame
840
1119
  module Service
841
1120
  module ApacheBasicProps
842
- attr_accessor :listen_port, :listen_port_https, :server_root
1121
+
843
1122
  end
844
1123
  end
845
1124
  end