vcap_services_base 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/lib/base/abstract.rb +11 -0
  3. data/lib/base/api/message.rb +31 -0
  4. data/lib/base/asynchronous_service_gateway.rb +529 -0
  5. data/lib/base/backup.rb +206 -0
  6. data/lib/base/barrier.rb +54 -0
  7. data/lib/base/base.rb +159 -0
  8. data/lib/base/base_async_gateway.rb +164 -0
  9. data/lib/base/base_job.rb +5 -0
  10. data/lib/base/catalog_manager_base.rb +67 -0
  11. data/lib/base/catalog_manager_v1.rb +225 -0
  12. data/lib/base/catalog_manager_v2.rb +291 -0
  13. data/lib/base/cloud_controller_services.rb +75 -0
  14. data/lib/base/datamapper_l.rb +148 -0
  15. data/lib/base/gateway.rb +167 -0
  16. data/lib/base/gateway_service_catalog.rb +68 -0
  17. data/lib/base/http_handler.rb +101 -0
  18. data/lib/base/job/async_job.rb +71 -0
  19. data/lib/base/job/config.rb +27 -0
  20. data/lib/base/job/lock.rb +153 -0
  21. data/lib/base/job/package.rb +112 -0
  22. data/lib/base/job/serialization.rb +365 -0
  23. data/lib/base/job/snapshot.rb +354 -0
  24. data/lib/base/node.rb +471 -0
  25. data/lib/base/node_bin.rb +154 -0
  26. data/lib/base/plan.rb +63 -0
  27. data/lib/base/provisioner.rb +1120 -0
  28. data/lib/base/provisioner_v1.rb +125 -0
  29. data/lib/base/provisioner_v2.rb +193 -0
  30. data/lib/base/service.rb +93 -0
  31. data/lib/base/service_advertiser.rb +184 -0
  32. data/lib/base/service_error.rb +122 -0
  33. data/lib/base/service_message.rb +94 -0
  34. data/lib/base/service_plan_change_set.rb +11 -0
  35. data/lib/base/simple_aop.rb +63 -0
  36. data/lib/base/snapshot_v2/snapshot.rb +227 -0
  37. data/lib/base/snapshot_v2/snapshot_client.rb +158 -0
  38. data/lib/base/snapshot_v2/snapshot_job.rb +95 -0
  39. data/lib/base/utils.rb +63 -0
  40. data/lib/base/version.rb +7 -0
  41. data/lib/base/warden/instance_utils.rb +161 -0
  42. data/lib/base/warden/node_utils.rb +205 -0
  43. data/lib/base/warden/service.rb +426 -0
  44. data/lib/base/worker_bin.rb +76 -0
  45. data/lib/vcap_services_base.rb +16 -0
  46. metadata +364 -0
@@ -0,0 +1,471 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require 'nats/client'
3
+ require 'vcap/component'
4
+ require 'fileutils'
5
+ require 'socket'
6
+
7
+ $:.unshift(File.dirname(__FILE__))
8
+ require 'base'
9
+ require 'service_message'
10
+ require 'datamapper_l'
11
+
12
+ class VCAP::Services::Base::Node < VCAP::Services::Base::Base
13
+ include VCAP::Services::Internal
14
+
15
+ def initialize(options)
16
+ super(options)
17
+ @node_id = options[:node_id]
18
+ @plan = options[:plan]
19
+ @capacity = options[:capacity]
20
+ @max_capacity = @capacity
21
+ @capacity_lock = Mutex.new
22
+ @migration_nfs = options[:migration_nfs]
23
+ @fqdn_hosts = options[:fqdn_hosts]
24
+ @op_time_limit = options[:op_time_limit]
25
+ @disabled_file = options[:disabled_file]
26
+ DataMapper::initialize_lock_file(options[:database_lock_file]) if options[:database_lock_file]
27
+
28
+ # A default supported version
29
+ # *NOTE: All services *MUST* override this to provide the actual supported versions
30
+ @supported_versions = options[:supported_versions] || []
31
+ z_interval = options[:z_interval] || 30
32
+ EM.add_periodic_timer(z_interval) do
33
+ EM.defer { update_varz }
34
+ end if @node_nats
35
+
36
+ # Defer 5 seconds to give service a change to wake up
37
+ EM.add_timer(5) do
38
+ EM.defer { update_varz }
39
+ end if @node_nats
40
+ end
41
+
42
+ def flavor
43
+ return "Node"
44
+ end
45
+
46
+ def on_connect_node
47
+ @logger.debug("#{service_description}: Connected to node mbus")
48
+
49
+ %w[provision unprovision bind unbind restore disable_instance
50
+ enable_instance import_instance update_instance cleanupnfs_instance purge_orphan
51
+ ].each do |op|
52
+ eval %[@node_nats.subscribe("#{service_name}.#{op}.#{@node_id}") { |msg, reply| EM.defer{ on_#{op}(msg, reply) } }]
53
+ end
54
+ %w[discover check_orphan].each do |op|
55
+ eval %[@node_nats.subscribe("#{service_name}.#{op}") { |msg, reply| EM.defer{ on_#{op}(msg, reply) } }]
56
+ end
57
+
58
+ pre_send_announcement
59
+ send_node_announcement
60
+ EM.add_periodic_timer(30) { send_node_announcement }
61
+ end
62
+
63
+ # raise an error if operation does not finish in time limit
64
+ # and perform a rollback action if rollback function is provided
65
+ def timing_exec(time_limit, rollback=nil)
66
+ return unless block_given?
67
+
68
+ start = Time.now
69
+ response = yield
70
+ if response && Time.now - start > time_limit
71
+ rollback.call(response) if rollback
72
+ raise ServiceError::new(ServiceError::NODE_OPERATION_TIMEOUT)
73
+ end
74
+ end
75
+
76
+ def on_provision(msg, reply)
77
+ @logger.debug("#{service_description}: Provision request: #{msg} from #{reply}")
78
+ response = ProvisionResponse.new
79
+ rollback = lambda do |res|
80
+ @logger.error("#{service_description}: Provision takes too long. Rollback for #{res.inspect}")
81
+ @capacity_lock.synchronize{ @capacity += capacity_unit } if unprovision(res.credentials["name"], [])
82
+ end
83
+
84
+ timing_exec(@op_time_limit, rollback) do
85
+ provision_req = ProvisionRequest.decode(msg)
86
+ plan = provision_req.plan
87
+ credentials = provision_req.credentials
88
+ version = provision_req.version
89
+ @logger.debug("#{service_description}: Provision Request Details - plan=#{plan}, credentials=#{credentials}, version=#{version}")
90
+ credential = provision(plan, credentials, version)
91
+ credential['node_id'] = @node_id
92
+ response.credentials = credential
93
+ @capacity_lock.synchronize{ @capacity -= capacity_unit }
94
+ @logger.debug("#{service_description}: Successfully provisioned service for request #{msg}: #{response.inspect}")
95
+ response
96
+ end
97
+ publish(reply, encode_success(response))
98
+ rescue => e
99
+ @logger.warn("Exception at on_provision #{e}")
100
+ publish(reply, encode_failure(response, e))
101
+ end
102
+
103
+ def on_unprovision(msg, reply)
104
+ @logger.debug("#{service_description}: Unprovision request: #{msg}.")
105
+ response = SimpleResponse.new
106
+ unprovision_req = UnprovisionRequest.decode(msg)
107
+ name = unprovision_req.name
108
+ bindings = unprovision_req.bindings
109
+ result = unprovision(name, bindings)
110
+ if result
111
+ publish(reply, encode_success(response))
112
+ @capacity_lock.synchronize{ @capacity += capacity_unit }
113
+ else
114
+ publish(reply, encode_failure(response))
115
+ end
116
+ rescue => e
117
+ @logger.warn("Exception at on_unprovision #{e}")
118
+ if e.http_status == ServiceError::HTTP_NOT_FOUND
119
+ publish(reply, encode_success(response))
120
+ else
121
+ publish(reply, encode_failure(response, e))
122
+ end
123
+ end
124
+
125
+ def on_bind(msg, reply)
126
+ @logger.debug("#{service_description}: Bind request: #{msg} from #{reply}")
127
+ response = BindResponse.new
128
+ rollback = lambda do |res|
129
+ @logger.error("#{service_description}: Binding takes too long. Rollback for #{res.inspect}")
130
+ unbind(res.credentials)
131
+ end
132
+
133
+ timing_exec(@op_time_limit, rollback) do
134
+ bind_message = BindRequest.decode(msg)
135
+ name = bind_message.name
136
+ bind_opts = bind_message.bind_opts
137
+ credentials = bind_message.credentials
138
+ response.credentials = bind(name, bind_opts, credentials)
139
+ response
140
+ end
141
+ publish(reply, encode_success(response))
142
+ rescue => e
143
+ @logger.warn("Exception at on_bind #{e}")
144
+ publish(reply, encode_failure(response, e))
145
+ end
146
+
147
+ def on_unbind(msg, reply)
148
+ @logger.debug("#{service_description}: Unbind request: #{msg} from #{reply}")
149
+ response = SimpleResponse.new
150
+ unbind_req = UnbindRequest.decode(msg)
151
+ result = unbind(unbind_req.credentials)
152
+ if result
153
+ publish(reply, encode_success(response))
154
+ else
155
+ publish(reply, encode_failure(response))
156
+ end
157
+ rescue => e
158
+ @logger.warn("Exception at on_unbind #{e}")
159
+ publish(reply, encode_failure(response, e))
160
+ end
161
+
162
+ def on_restore(msg, reply)
163
+ @logger.debug("#{service_description}: Restore request: #{msg} from #{reply}")
164
+ response = SimpleResponse.new
165
+ restore_message = RestoreRequest.decode(msg)
166
+ instance_id = restore_message.instance_id
167
+ backup_path = restore_message.backup_path
168
+ result = restore(instance_id, backup_path)
169
+ if result
170
+ publish(reply, encode_success(response))
171
+ else
172
+ publish(reply, encode_failure(response))
173
+ end
174
+ rescue => e
175
+ @logger.warn("Exception at on_restore #{e}")
176
+ publish(reply, encode_failure(response, e))
177
+ end
178
+
179
+ # Disable and dump instance
180
+ def on_disable_instance(msg, reply)
181
+ @logger.debug("#{service_description}: Disable instance #{msg} request from #{reply}")
182
+ response = SimpleResponse.new
183
+ request = Yajl::Parser.parse(msg)
184
+ prov_handle, binding_handles = request
185
+ prov_cred = prov_handle["credentials"]
186
+ binding_creds = get_all_bindings(binding_handles)
187
+ file_path = get_migration_folder(prov_handle["service_id"])
188
+ FileUtils.mkdir_p(file_path)
189
+ result = disable_instance(prov_cred, binding_creds)
190
+ if result
191
+ # Do dump together with disable for simpler migration logic
192
+ result = dump_instance(prov_cred, binding_creds, file_path)
193
+ if result
194
+ publish(reply, encode_success(response))
195
+ else
196
+ publish(reply, encode_failure(response))
197
+ end
198
+ else
199
+ publish(reply, encode_failure(response))
200
+ end
201
+ rescue => e
202
+ @logger.warn("Exception at on_disable_instance #{e}")
203
+ publish(reply, encode_failure(response, e))
204
+ end
205
+
206
+ # Enable instance, the opposite operation of disable
207
+ def on_enable_instance(msg, reply)
208
+ @logger.debug("#{service_description}: enable instance #{msg} request from #{reply}")
209
+ response = SimpleResponse.new
210
+ request = Yajl::Parser.parse(msg)
211
+ prov_handle, binding_handles = request
212
+ prov_cred = prov_handle["credentials"]
213
+ binding_creds_hash = get_all_bindings_with_option(binding_handles)
214
+ result = enable_instance(prov_cred, binding_creds_hash)
215
+ if result
216
+ publish(reply, encode_success(response))
217
+ else
218
+ publish(reply, encode_failure(response))
219
+ end
220
+ rescue => e
221
+ @logger.warn("Exception at on_enable_instance #{e}")
222
+ publish(reply, encode_failure(response, e))
223
+ end
224
+
225
+ # Import the generated data
226
+ def on_import_instance(msg, reply)
227
+ @logger.debug("#{service_description}: import instance #{msg} request from #{reply}")
228
+ response = SimpleResponse.new
229
+ request = Yajl::Parser.parse(msg)
230
+ prov_handle, binding_handles = request
231
+ prov_cred = prov_handle["credentials"]
232
+ binding_creds_hash = get_all_bindings_with_option(binding_handles)
233
+ plan = prov_handle["configuration"]["plan"]
234
+ file_path = get_migration_folder(prov_handle["service_id"])
235
+ result = import_instance(prov_cred, binding_creds_hash, file_path, plan)
236
+ if result
237
+ publish(reply, encode_success(response))
238
+ else
239
+ publish(reply, encode_failure(response))
240
+ end
241
+ rescue => e
242
+ @logger.warn("Exception at on_import_instance #{e}")
243
+ publish(reply, encode_failure(response, e))
244
+ end
245
+
246
+ # Update credentials in destination node of migration
247
+ def on_update_instance(msg, reply)
248
+ @logger.debug("#{service_description}: update instance #{msg} request from #{reply}")
249
+ request = Yajl::Parser.parse(msg)
250
+ prov_handle, binding_handles = request
251
+ prov_cred = prov_handle["credentials"]
252
+ binding_creds_hash = get_all_bindings_with_option(binding_handles)
253
+ result = update_instance(prov_cred, binding_creds_hash)
254
+ # Need decrease the capacity in destination node when finish migration
255
+ @capacity_lock.synchronize{ @capacity -= capacity_unit }
256
+ prov_cred, binding_creds_hash = result
257
+ # Update node_id in provision credentials
258
+ prov_cred["node_id"] = @node_id
259
+ handles = []
260
+ prov_handle["credentials"] = prov_cred
261
+ handles << prov_handle
262
+ binding_handles.each do |handle|
263
+ handle["credentials"] = binding_creds_hash[handle["service_id"]]["credentials"]
264
+ handles << handle
265
+ end
266
+ publish(reply, Yajl::Encoder.encode(handles))
267
+ rescue => e
268
+ @logger.warn("Exception at on_update_instance #{e}")
269
+ response = SimpleResponse.new
270
+ publish(reply, encode_failure(response, e))
271
+ end
272
+
273
+ # Cleanup nfs folder which contains migration data
274
+ def on_cleanupnfs_instance(msg, reply)
275
+ @logger.debug("#{service_description}: cleanup nfs request #{msg} from #{reply}")
276
+ response = SimpleResponse.new
277
+ request = Yajl::Parser.parse(msg)
278
+ prov_handle, _ = request
279
+ FileUtils.rm_rf(get_migration_folder(prov_handle["service_id"]))
280
+ publish(reply, encode_success(response))
281
+ rescue => e
282
+ @logger.warn("Exception at on_cleanupnfs_instance #{e}")
283
+ publish(reply, encode_failure(response, e))
284
+ end
285
+
286
+ # Send all handles to gateway to check orphan
287
+ def on_check_orphan(msg, reply)
288
+ @logger.debug("#{service_description}: Request to check orphan")
289
+ live_ins_list = all_instances_list
290
+ live_bind_list = all_bindings_list
291
+ handles_size = @max_nats_payload - 200
292
+
293
+ group_handles_in_json(live_ins_list, live_bind_list, handles_size) do |ins_list, bind_list|
294
+ request = NodeHandlesReport.new
295
+ request.instances_list = ins_list
296
+ request.bindings_list = bind_list
297
+ request.node_id = @node_id
298
+ publish("#{service_name}.node_handles", request.encode)
299
+ end
300
+ rescue => e
301
+ @logger.warn("Exception at on_check_orphan #{e}")
302
+ end
303
+
304
+ def on_purge_orphan(msg, reply)
305
+ @logger.debug("#{service_description}: Request to purge orphan" )
306
+ request = PurgeOrphanRequest.decode(msg)
307
+ purge_orphan(request.orphan_ins_list,request.orphan_binding_list)
308
+ rescue => e
309
+ @logger.warn("Exception at on_purge_orphan #{e}")
310
+ end
311
+
312
+ def purge_orphan(oi_list,ob_list)
313
+ oi_list.each do |ins|
314
+ begin
315
+ @logger.debug("Unprovision orphan instance #{ins}")
316
+ @capacity_lock.synchronize{ @capacity += capacity_unit } if unprovision(ins,[])
317
+ rescue => e
318
+ @logger.debug("Error on purge orphan instance #{ins}: #{e}")
319
+ end
320
+ end
321
+
322
+ ob_list.each do |credential|
323
+ begin
324
+ @logger.debug("Unbind orphan binding #{credential}")
325
+ unbind(credential)
326
+ rescue => e
327
+ @logger.debug("Error on purge orphan binding #{credential}: #{e}")
328
+ end
329
+ end
330
+ end
331
+
332
+ # Subclass must overwrite this method to enable check orphan instance feature.
333
+ # Otherwise it will not check orphan instance
334
+ # The return value should be a list of instance name(handle["service_id"]).
335
+ def all_instances_list
336
+ []
337
+ end
338
+
339
+ # Subclass must overwrite this method to enable check orphan binding feature.
340
+ # Otherwise it will not check orphan bindings
341
+ # The return value should be a list of binding credentials
342
+ # Binding credential will be the argument for unbind method
343
+ # And it should have at least username & name property for base code
344
+ # to find the orphans
345
+ def all_bindings_list
346
+ []
347
+ end
348
+
349
+ # Get the tmp folder for migration
350
+ def get_migration_folder(instance)
351
+ File.join(@migration_nfs, 'migration', service_name, instance)
352
+ end
353
+
354
+ def get_all_bindings(handles)
355
+ binding_creds = []
356
+ handles.each do |handle|
357
+ binding_creds << handle["credentials"]
358
+ end
359
+ binding_creds
360
+ end
361
+
362
+ def get_all_bindings_with_option(handles)
363
+ binding_creds_hash = {}
364
+ handles.each do |handle|
365
+ value = {
366
+ "credentials" => handle["credentials"],
367
+ "binding_options" => nil
368
+ }
369
+ value["binding_options"] = handle["configuration"]["data"]["binding_options"] if handle["configuration"].has_key?("data")
370
+ binding_creds_hash[handle["service_id"]] = value
371
+ end
372
+ binding_creds_hash
373
+ end
374
+
375
+ def on_discover(msg, reply)
376
+ send_node_announcement(msg, reply)
377
+ end
378
+
379
+ def pre_send_announcement
380
+ end
381
+
382
+ def disabled?
383
+ File.exist?(@disabled_file)
384
+ end
385
+
386
+ def send_node_announcement(msg=nil, reply=nil)
387
+ if disabled?
388
+ @logger.info("#{service_description}: Not sending announcement because node is disabled")
389
+ return
390
+ end
391
+ unless node_ready?
392
+ @logger.debug("#{service_description}: Not ready to send announcement")
393
+ return
394
+ end
395
+ @logger.debug("#{service_description}: Sending announcement for #{reply || "everyone"}")
396
+ req = nil
397
+ req = Yajl::Parser.parse(msg) if msg
398
+ if !req || req["plan"] == @plan
399
+ a = announcement
400
+ a[:id] = @node_id
401
+ a[:plan] = @plan
402
+ a[:supported_versions] = @supported_versions
403
+ publish(reply || "#{service_name}.announce", Yajl::Encoder.encode(a))
404
+ end
405
+ rescue => e
406
+ @logger.warn("Exception at send_node_announcement #{e}")
407
+ end
408
+
409
+ def node_ready?()
410
+ # Service Node subclasses can override this method if they depend
411
+ # on some external service in order to operate; for example, MySQL
412
+ # and Postgresql require a connection to the underlying server.
413
+ true
414
+ end
415
+
416
+ def varz_details
417
+ # Service Node subclasses may want to override this method to
418
+ # provide service specific data beyond what is returned by their
419
+ # "announcement" method.
420
+ return announcement
421
+ end
422
+
423
+ def capacity_unit
424
+ # subclasses could overwrite this method to re-define
425
+ # the capacity unit decreased/increased by provision/unprovision
426
+ 1
427
+ end
428
+
429
+ # Helper
430
+ def encode_success(response)
431
+ response.success = true
432
+ response.encode
433
+ end
434
+
435
+ def encode_failure(response, error=nil)
436
+ response.success = false
437
+ if error.nil? || !error.is_a?(ServiceError)
438
+ error = ServiceError.new(ServiceError::INTERNAL_ERROR)
439
+ end
440
+ response.error = error.to_hash
441
+ response.encode
442
+ end
443
+
444
+ def get_host
445
+ @fqdn_hosts ? Socket.gethostname : @local_ip
446
+ end
447
+
448
+ # Service Node subclasses must implement the following methods
449
+
450
+ # provision(plan) --> {name, host, port, user, password}, {version}
451
+ abstract :provision
452
+
453
+ # unprovision(name) --> void
454
+ abstract :unprovision
455
+
456
+ # bind(name, bind_opts) --> {host, port, login, secret}
457
+ abstract :bind
458
+
459
+ # unbind(credentials) --> void
460
+ abstract :unbind
461
+
462
+ # announcement() --> { any service-specific announcement details }
463
+ abstract :announcement
464
+
465
+ # service_name() --> string
466
+ # (inhereted from VCAP::Services::Base::Base)
467
+
468
+ # <action>_instance(prov_credential, binding_credentials) --> true for success and nil for fail
469
+ abstract :disable_instance, :dump_instance, :import_instance, :enable_instance, :update_instance
470
+
471
+ end