vcap_services_base 0.2.10

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 (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