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,154 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+ require 'optparse'
5
+ require 'yaml'
6
+ require 'steno'
7
+
8
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', '..')
9
+ require 'vcap/common'
10
+
11
+ $LOAD_PATH.unshift File.dirname(__FILE__)
12
+ require 'abstract'
13
+
14
+ module VCAP
15
+ module Services
16
+ module Base
17
+ end
18
+ end
19
+ end
20
+
21
+ class VCAP::Services::Base::NodeBin
22
+
23
+ abstract :default_config_file
24
+ abstract :node_class
25
+ abstract :additional_config
26
+
27
+ module Boolean; end
28
+ class ::TrueClass; include Boolean; end
29
+ class ::FalseClass; include Boolean; end
30
+
31
+ def start
32
+ config_file = default_config_file
33
+
34
+ OptionParser.new do |opts|
35
+ opts.banner = "Usage: #{$0.split(/\//)[-1]} [options]"
36
+ opts.on("-c", "--config [ARG]", "Configuration File") do |opt|
37
+ config_file = opt
38
+ end
39
+ opts.on("-h", "--help", "Help") do
40
+ puts opts
41
+ exit
42
+ end
43
+ end.parse!
44
+
45
+ begin
46
+ config = YAML.load_file(config_file)
47
+ rescue => e
48
+ puts "Could not read configuration file: #{e}"
49
+ exit
50
+ end
51
+
52
+ options = {
53
+ :index => parse_property(config, "index", Integer, :optional => true),
54
+ :plan => parse_property(config, "plan", String, :optional => true, :default => "free"),
55
+ :capacity => parse_property(config, "capacity", Integer, :optional => true, :default => 200),
56
+ :ip_route => parse_property(config, "ip_route", String, :optional => true),
57
+ :node_id => parse_property(config, "node_id", String),
58
+ :z_interval => parse_property(config, "z_interval", Integer, :optional => true),
59
+ :mbus => parse_property(config, "mbus", String),
60
+ :local_db => parse_property(config, "local_db", String),
61
+ :migration_nfs => parse_property(config, "migration_nfs", String, :optional => true),
62
+ :max_nats_payload => parse_property(config, "max_nats_payload", Integer, :optional => true),
63
+ :fqdn_hosts => parse_property(config, "fqdn_hosts", Boolean, :optional => true, :default => false),
64
+ :op_time_limit => parse_property(config, "op_time_limit", Integer, :optional => true, :default => 6),
65
+ :supported_versions => parse_property(config, "supported_versions", Array),
66
+ :default_version => parse_property(config, "default_version", String),
67
+ :max_clients => parse_property(config, "max_clients", Integer, :optional => true),
68
+ :database_lock_file => parse_property(config, "database_lock_file", String, :optional => true, :default => "/var/vcap/sys/run/LOCK"),
69
+ :disabled_file => parse_property(config, "disabled_file", String, :optional => true, :default => "/var/vcap/store/DISABLED"),
70
+ # Wardenized service configuration
71
+ :base_dir => parse_property(config, "base_dir", String, :optional => true),
72
+ :service_log_dir => parse_property(config, "service_log_dir", String, :optional => true),
73
+ :service_common_dir => parse_property(config, "service_common_dir", String, :optional => true, :default => "/var/vcap/store/common"),
74
+ :service_bin_dir => parse_property(config, "service_bin_dir", Hash, :optional => true),
75
+ :image_dir => parse_property(config, "image_dir", String, :optional => true),
76
+ :port_range => parse_property(config, "port_range", Range, :optional => true),
77
+ :filesystem_quota => parse_property(config, "filesystem_quota", Boolean, :optional => true, :default => false),
78
+ :service_start_timeout => parse_property(config, "service_start_timeout", Integer, :optional => true, :default => 3),
79
+ :service_status_timeout => parse_property(config, "service_status_timeout", Integer, :optional => true, :default => 3),
80
+ :max_memory => parse_property(config, "max_memory", Numeric, :optional => true),
81
+ :memory_overhead => parse_property(config, "memory_overhead", Numeric, :optional => true, :default => 0.0),
82
+ :max_disk => parse_property(config, "max_disk", Numeric, :optional => true, :default => 128.0),
83
+ :disk_overhead => parse_property(config, "disk_overhead", Numeric, :optional => true, :default => 0.0),
84
+ :m_interval => parse_property(config, "m_interval", Integer, :optional => true, :default => 10),
85
+ :m_actions => parse_property(config, "m_actions", Array, :optional => true, :default => []),
86
+ :m_failed_times => parse_property(config, "m_failed_times", Integer, :optional => true, :default => 3),
87
+ :warden_socket_path => parse_property(config, "warden_socket_path", String, :optional => true),
88
+ }
89
+ # Workaround for services that support running the service both inside and outside warden
90
+ use_warden = parse_property(config, "use_warden", Boolean, :optional => true, :default => false)
91
+ if use_warden
92
+ warden_config = parse_property(config, "warden", Hash, :optional => true)
93
+ options[:service_log_dir] = parse_property(warden_config, "service_log_dir", String)
94
+ options[:service_common_dir] = parse_property(warden_config, "service_common_dir", String, :optional => true, :default => "/var/vcap/store/common")
95
+ options[:service_bin_dir] = parse_property(warden_config, "service_bin_dir", Hash, :optional => true)
96
+ options[:port_range] = parse_property(warden_config, "port_range", Range)
97
+ options[:image_dir] = parse_property(warden_config, "image_dir", String)
98
+ options[:filesystem_quota] = parse_property(warden_config, "filesystem_quota", Boolean, :optional => true, :default => false)
99
+ options[:service_start_timeout] = parse_property(warden_config, "service_start_timeout", Integer, :optional => true, :default => 3)
100
+ options[:service_status_timeout] = parse_property(warden_config, "service_status_timeout", Integer, :optional => true, :default => 3)
101
+ end
102
+
103
+ logging_config = Steno::Config.from_hash(config["logging"])
104
+ Steno.init(logging_config)
105
+ # Use the node id for logger identity name.
106
+ options[:logger] = Steno.logger(options[:node_id])
107
+ @logger = options[:logger]
108
+
109
+ options = additional_config(options, config)
110
+
111
+ EM.error_handler do |ex|
112
+ @logger.fatal("#{ex} #{ex.backtrace.join("|")}")
113
+ exit
114
+ end
115
+
116
+ pid_file = parse_property(config, "pid", String)
117
+ begin
118
+ FileUtils.mkdir_p(File.dirname(pid_file))
119
+ rescue => e
120
+ @logger.fatal "Can't create pid directory, exiting: #{e}"
121
+ exit
122
+ end
123
+ File.open(pid_file, 'w') { |f| f.puts "#{Process.pid}" }
124
+
125
+ EM.run do
126
+ node = node_class.new(options)
127
+ trap("INT") {shutdown(node)}
128
+ trap("TERM") {shutdown(node)}
129
+ end
130
+ end
131
+
132
+ def shutdown(node)
133
+ @logger.info("Begin to shutdown node")
134
+ node.shutdown
135
+ @logger.info("End to shutdown node")
136
+ EM.stop
137
+ end
138
+
139
+ def parse_property(hash, key, type, options = {})
140
+ obj = hash[key]
141
+ if obj.nil?
142
+ raise "Missing required option: #{key}" unless options[:optional]
143
+ options[:default]
144
+ elsif type == Range
145
+ raise "Invalid Range object: #{obj}" unless obj.kind_of?(Hash)
146
+ first, last = obj["first"], obj["last"]
147
+ raise "Invalid Range object: #{obj}" unless first.kind_of?(Integer) and last.kind_of?(Integer)
148
+ Range.new(first, last)
149
+ else
150
+ raise "Invalid #{type} object: #{obj}" unless obj.kind_of?(type)
151
+ obj
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,63 @@
1
+ module VCAP::Services
2
+ class Plan
3
+ attr_reader :name, :guid, :description, :free, :unique_id, :extra
4
+ attr_writer :guid
5
+
6
+ def initialize(options)
7
+ @unique_id = options.fetch(:unique_id)
8
+ @guid = options[:guid]
9
+ @name = options[:name]
10
+ @description = options[:description]
11
+ @free = options[:free]
12
+ @extra = options[:extra]
13
+ @public = options.fetch(:public, true)
14
+ end
15
+
16
+ def to_hash
17
+ {
18
+ 'unique_id' => @unique_id,
19
+ 'name' => @name,
20
+ 'description' => @description,
21
+ 'free' => @free,
22
+ 'extra' => @extra,
23
+ 'public' => @public,
24
+ }
25
+ end
26
+
27
+ def get_update_hash(service_guid)
28
+ plan_as_hash = self.to_hash
29
+ plan_as_hash['service_guid'] = service_guid
30
+ plan_as_hash.delete('unique_id')
31
+ plan_as_hash.delete('public')
32
+ return plan_as_hash
33
+ end
34
+
35
+ def get_add_hash(service_guid)
36
+ plan_as_hash = self.to_hash
37
+ plan_as_hash['service_guid'] = service_guid
38
+ return plan_as_hash
39
+ end
40
+
41
+ def self.plan_hash_as_plan_array(plans)
42
+ plan_array = []
43
+ return plan_array unless plans
44
+ if plans.first.is_a?(Plan)
45
+ return plans
46
+ else
47
+ plans.each do |_, v|
48
+ plan_array << Plan.new(v)
49
+ end
50
+ end
51
+ plan_array
52
+ end
53
+
54
+ def self.plans_array_to_hash(plans_array)
55
+ return [] unless plans_array
56
+ plans_array_hash = []
57
+ plans_array.each do |plan|
58
+ plans_array_hash << plan.to_hash
59
+ end
60
+ plans_array_hash
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,1120 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (c) 2009-2011 VMware, Inc.
3
+
4
+ require "set"
5
+ require "data_mapper"
6
+
7
+ $LOAD_PATH.unshift File.dirname(__FILE__)
8
+ require 'base/base'
9
+ require 'base/simple_aop'
10
+ require 'base/job/async_job'
11
+ require 'base/job/snapshot'
12
+ require 'base/job/serialization'
13
+ require 'base/snapshot_v2/snapshot_client'
14
+ require 'barrier'
15
+ require 'service_message'
16
+ require 'securerandom'
17
+
18
+ class VCAP::Services::Base::Provisioner < VCAP::Services::Base::Base
19
+ include VCAP::Services::Internal
20
+ include VCAP::Services::Base::AsyncJob
21
+ include VCAP::Services::Base::AsyncJob::Snapshot
22
+ include Before
23
+
24
+ BARRIER_TIMEOUT = 2
25
+ MASKED_PASSWORD = '********'
26
+
27
+ def snapshot_client
28
+ @snapshot_client ||= VCAP::Services::Base::SnapshotV2::SnapshotClient.new(options.fetch(:snapshot_db))
29
+ end
30
+
31
+ attr_reader :options
32
+
33
+ def initialize(options)
34
+ super(options)
35
+ @options = options
36
+ @node_timeout = options[:node_timeout]
37
+ @nodes = {}
38
+ @provision_refs = Hash.new(0)
39
+ @instance_handles_CO = {}
40
+ @binding_handles_CO = {}
41
+ @responses_metrics = {
42
+ :responses_xxx => 0,
43
+ :responses_2xx => 0,
44
+ :responses_3xx => 0,
45
+ :responses_4xx => 0,
46
+ :responses_5xx => 0,
47
+ }
48
+ @plan_mgmt = options[:plan_management] && options[:plan_management][:plans] || {}
49
+
50
+ gw_version = options[:cc_api_version]
51
+ if gw_version == "v1"
52
+ require 'provisioner_v1'
53
+ extend VCAP::Services::Base::ProvisionerV1
54
+ @prov_svcs = {}
55
+ elsif gw_version == "v2"
56
+ require 'provisioner_v2'
57
+ extend VCAP::Services::Base::ProvisionerV2
58
+ @service_instances = {}
59
+ @service_bindings = {}
60
+ else
61
+ raise "unknown cc api version: #{gw_version}"
62
+ end
63
+
64
+ init_service_extensions
65
+
66
+ @staging_orphan_instances = {}
67
+ @staging_orphan_bindings = {}
68
+ @final_orphan_instances = {}
69
+ @final_orphan_bindings = {}
70
+
71
+ z_interval = options[:z_interval] || 30
72
+
73
+ EM.add_periodic_timer(z_interval) do
74
+ update_varz
75
+ end if @node_nats
76
+
77
+ # Defer 5 seconds to give service a change to wake up
78
+ EM.add_timer(5) do
79
+ update_varz
80
+ end if @node_nats
81
+
82
+ EM.add_periodic_timer(60) { process_nodes }
83
+ end
84
+
85
+ def flavor
86
+ 'Provisioner'
87
+ end
88
+
89
+ def create_redis(opt)
90
+ redis_client = ::Redis.new(opt)
91
+ raise "Can't connect to redis:#{opt.inspect}" unless redis_client
92
+ redis_client
93
+ end
94
+
95
+ def init_service_extensions
96
+ @extensions = {}
97
+ @plan_mgmt.each do |plan, value|
98
+ lifecycle = value[:lifecycle]
99
+ next unless lifecycle
100
+ @extensions[plan] ||= {}
101
+ @extensions[plan][:snapshot] = lifecycle.has_key? :snapshot
102
+ %w(serialization job).each do |ext|
103
+ ext = ext.to_sym
104
+ @extensions[plan][ext] = true if lifecycle[ext] == "enable"
105
+ end
106
+ end
107
+ end
108
+
109
+ def update_responses_metrics(status)
110
+ return unless status.is_a? Fixnum
111
+
112
+ metric = :responses_xxx
113
+ if status >=200 and status <300
114
+ metric = :responses_2xx
115
+ elsif status >=300 and status <400
116
+ metric = :responses_3xx
117
+ elsif status >=400 and status <500
118
+ metric = :responses_4xx
119
+ elsif status >=500 and status <600
120
+ metric = :responses_5xx
121
+ end
122
+ @responses_metrics[metric] += 1
123
+ rescue => e
124
+ @logger.warn("Failed update responses metrics: #{e}")
125
+ end
126
+
127
+ def verify_handle_format(handle)
128
+ return nil unless handle
129
+ return nil unless handle.is_a? Hash
130
+
131
+ VCAP::Services::Internal::ServiceHandle.new(handle)
132
+ true
133
+ rescue => e
134
+ @logger.warn("Verify handle #{handle} failed:#{e}")
135
+ return nil
136
+ end
137
+
138
+ def process_nodes
139
+ @nodes.delete_if do |id, node|
140
+ stale = (Time.now.to_i - node["time"]) > 300
141
+ @provision_refs.delete(id) if stale
142
+ stale
143
+ end
144
+ end
145
+
146
+ def pre_send_announcement
147
+ addition_opts = self.options[:additional_options]
148
+ if addition_opts
149
+ if addition_opts[:resque]
150
+ # Initial AsyncJob module
151
+ job_repo_setup()
152
+ VCAP::Services::Base::AsyncJob::Snapshot.redis_connect
153
+ end
154
+ end
155
+ end
156
+
157
+ # update version information of existing instances
158
+ def update_version_info(current_version)
159
+ @logger.debug("[#{service_description}] Update version of existing instances to '#{current_version}'")
160
+
161
+ updated_prov_handles = []
162
+ get_all_instance_handles do |handle|
163
+ next if handle[:configuration].has_key? "version"
164
+
165
+ updated_prov_handle = {}
166
+ # update_handle_callback need string as key
167
+ handle.each {|k, v| updated_prov_handle[k.to_s] = v.deep_dup}
168
+ updated_prov_handle["configuration"]["version"] = current_version
169
+
170
+ updated_prov_handles << updated_prov_handle
171
+ end
172
+
173
+ f = Fiber.new do
174
+ failed, successful = 0, 0
175
+ updated_prov_handles.each do |handle|
176
+ @logger.debug("[#{service_description}] trying to update handle: #{handle}")
177
+ # NOTE: serialized update_handle in case CC/router overload
178
+ res = fiber_update_handle(handle)
179
+ if res
180
+ @logger.info("Successful update version of handle:#{handle}")
181
+ successful += 1
182
+ else
183
+ @logger.error("Failed to update version of handle:#{handle}")
184
+ failed += 1
185
+ end
186
+ end
187
+ @logger.info("Result of update handle version: #{successful} successful, #{failed} failed.")
188
+ end
189
+ f.resume
190
+ rescue => e
191
+ @logger.error("Unexpected error when update version info: #{e}, #{e.backtrace.join('|')}")
192
+ end
193
+
194
+ def fiber_update_handle(updated_handle)
195
+ f = Fiber.current
196
+
197
+ @update_handle_callback.call(updated_handle) {|res| f.resume(res)}
198
+
199
+ Fiber.yield
200
+ end
201
+
202
+ def on_connect_node
203
+ @logger.debug("[#{service_description}] Connected to node mbus..")
204
+ %w[announce node_handles handles update_service_handle].each do |op|
205
+ eval %[@node_nats.subscribe("#{service_name}.#{op}") { |msg, reply| on_#{op}(msg, reply) }]
206
+ end
207
+
208
+ pre_send_announcement()
209
+ @node_nats.publish("#{service_name}.discover")
210
+ end
211
+
212
+ def on_announce(msg, reply=nil)
213
+ @logger.debug("[#{service_description}] Received node announcement: #{msg.inspect}")
214
+ announce_message = Yajl::Parser.parse(msg)
215
+ if announce_message["id"]
216
+ id = announce_message["id"]
217
+ announce_message["time"] = Time.now.to_i
218
+ if @provision_refs[id] > 0
219
+ announce_message['available_capacity'] = @nodes[id]['available_capacity']
220
+ end
221
+ @nodes[id] = announce_message
222
+ end
223
+ end
224
+
225
+ def on_node_handles(msg, reply)
226
+ @logger.debug("[#{service_description}] Received node handles")
227
+ response = NodeHandlesReport.decode(msg)
228
+ nid = response.node_id
229
+ @staging_orphan_instances[nid] ||= []
230
+ @staging_orphan_bindings[nid] ||= []
231
+ response.instances_list.each do |ins|
232
+ @staging_orphan_instances[nid] << ins unless @instance_handles_CO.has_key?(ins)
233
+ end
234
+ response.bindings_list.each do |bind|
235
+ user = bind["username"] || bind["user"]
236
+ next unless user
237
+ key = bind["name"] + user
238
+ @staging_orphan_bindings[nid] << bind unless @binding_handles_CO.has_key?(key)
239
+ end
240
+ oi_count = @staging_orphan_instances.values.reduce(0) { |m, v| m += v.count }
241
+ ob_count = @staging_orphan_bindings.values.reduce(0) { |m, v| m += v.count }
242
+ @logger.debug("Staging Orphans: Instances: #{oi_count}; Bindings: #{ob_count}")
243
+ rescue => e
244
+ @logger.warn("Exception at on_node_handles #{e}")
245
+ end
246
+
247
+ def check_orphan(handles, &blk)
248
+ @logger.debug("[#{service_description}] Check if there are orphans")
249
+ @staging_orphan_instances = {}
250
+ @staging_orphan_bindings = {}
251
+ @instance_handles_CO, @binding_handles_CO = indexing_handles(handles.deep_dup)
252
+ @node_nats.publish("#{service_name}.check_orphan","Send Me Handles")
253
+ blk.call(success)
254
+ rescue => e
255
+ @logger.warn("Exception at check_orphan #{e}")
256
+ if e.instance_of? ServiceError
257
+ blk.call(failure(e))
258
+ else
259
+ blk.call(internal_fail)
260
+ end
261
+ end
262
+
263
+ def double_check_orphan(handles)
264
+ @logger.debug("[#{service_description}] Double check the orphan result")
265
+ ins_handles, bin_handles = indexing_handles(handles)
266
+ @final_orphan_instances.clear
267
+ @final_orphan_bindings.clear
268
+
269
+ @staging_orphan_instances.each do |nid, oi_list|
270
+ @final_orphan_instances[nid] ||= []
271
+ oi_list.each do |oi|
272
+ @final_orphan_instances[nid] << oi unless ins_handles.has_key?(oi)
273
+ end
274
+ end
275
+ @staging_orphan_bindings.each do |nid, ob_list|
276
+ @final_orphan_bindings[nid] ||= []
277
+ ob_list.each do |ob|
278
+ user = ob["username"] || ob["user"]
279
+ next unless user
280
+ key = ob["name"] + user
281
+ @final_orphan_bindings[nid] << ob unless bin_handles.has_key?(key)
282
+ end
283
+ end
284
+ oi_count = @final_orphan_instances.values.reduce(0) { |m, v| m += v.count }
285
+ ob_count = @final_orphan_bindings.values.reduce(0) { |m, v| m += v.count }
286
+ @logger.debug("Final Orphans: Instances: #{oi_count}; Bindings: #{ob_count}")
287
+ rescue => e
288
+ @logger.warn("Exception at double_check_orphan #{e}")
289
+ end
290
+
291
+ def purge_orphan(orphan_ins_hash,orphan_bind_hash, &blk)
292
+ @logger.debug("[#{service_description}] Purge orphans for given list")
293
+ handles_size = @max_nats_payload - 200
294
+
295
+ send_purge_orphan_request = Proc.new do |node_id, i_list, b_list|
296
+ group_handles_in_json(i_list, b_list, handles_size) do |ins_list, bind_list|
297
+ @logger.debug("[#{service_description}] Purge orphans for #{node_id} instances: #{ins_list.count} bindings: #{bind_list.count}")
298
+ req = PurgeOrphanRequest.new
299
+ req.orphan_ins_list = ins_list
300
+ req.orphan_binding_list = bind_list
301
+ @node_nats.publish("#{service_name}.purge_orphan.#{node_id}", req.encode)
302
+ end
303
+ end
304
+
305
+ orphan_ins_hash.each do |nid, oi_list|
306
+ ob_list = orphan_bind_hash.delete(nid) || []
307
+ send_purge_orphan_request.call(nid, oi_list, ob_list)
308
+ end
309
+
310
+ orphan_bind_hash.each do |nid, ob_list|
311
+ send_purge_orphan_request.call(nid, [], ob_list)
312
+ end
313
+ blk.call(success)
314
+ rescue => e
315
+ @logger.warn("Exception at purge_orphan #{e}")
316
+ if e.instance_of? ServiceError
317
+ blk.call(failure(e))
318
+ else
319
+ blk.call(internal_fail)
320
+ end
321
+ end
322
+
323
+ def unprovision_service(instance_id, &blk)
324
+ @logger.debug("[#{service_description}] Unprovision service #{instance_id}")
325
+ begin
326
+ svc = get_instance_handle(instance_id)
327
+ @logger.debug("[#{service_description}] Unprovision service #{instance_id} found instance: #{svc}")
328
+ raise ServiceError.new(ServiceError::NOT_FOUND, "instance_id #{instance_id}") if svc.nil?
329
+
330
+ node_id = svc[:credentials]["node_id"]
331
+ raise "Cannot find node_id for #{instance_id}" if node_id.nil?
332
+
333
+ bindings = find_instance_bindings(instance_id)
334
+ @logger.debug("[#{service_description}] Unprovisioning instance #{instance_id} from #{node_id}")
335
+ request = UnprovisionRequest.new
336
+ request.name = instance_id
337
+ request.bindings = bindings.map{|h| h[:credentials]}
338
+ @logger.debug("[#{service_description}] Sending request #{request}")
339
+ subscription = nil
340
+ timer = EM.add_timer(@node_timeout) {
341
+ @node_nats.unsubscribe(subscription)
342
+ blk.call(timeout_fail)
343
+ }
344
+ subscription =
345
+ @node_nats.request(
346
+ "#{service_name}.unprovision.#{node_id}", request.encode
347
+ ) do |msg|
348
+ # Delete local entries
349
+ delete_instance_handle(svc)
350
+ bindings.each do |binding|
351
+ delete_binding_handle(binding)
352
+ end
353
+
354
+ EM.cancel_timer(timer)
355
+ @node_nats.unsubscribe(subscription)
356
+ opts = SimpleResponse.decode(msg)
357
+ if opts.success
358
+ blk.call(success())
359
+ else
360
+ blk.call(wrap_error(opts))
361
+ end
362
+ end
363
+ rescue => e
364
+ if e.instance_of? ServiceError
365
+ blk.call(failure(e))
366
+ else
367
+ @logger.warn("Exception at unprovision_service #{e}")
368
+ blk.call(internal_fail)
369
+ end
370
+ end
371
+ end
372
+
373
+ def provision_service(request, prov_handle=nil, &blk)
374
+ @logger.debug("[#{service_description}] Attempting to provision instance (request=#{request.extract})")
375
+ subscription = nil
376
+ plan = request.plan || "free"
377
+ version = request.version
378
+
379
+ plan_nodes = @nodes.select{ |_, node| node["plan"] == plan}.values
380
+
381
+ @logger.debug("[#{service_description}] Picking version nodes from the following #{plan_nodes.count} \'#{plan}\' plan nodes: #{plan_nodes}")
382
+ if plan_nodes.count > 0
383
+ allow_over_provisioning = @plan_mgmt[plan.to_sym] && @plan_mgmt[plan.to_sym][:allow_over_provisioning] || false
384
+
385
+ version_nodes = plan_nodes.select{ |node|
386
+ node["supported_versions"] != nil && node["supported_versions"].include?(version)
387
+ }
388
+ @logger.debug("[#{service_description}] #{version_nodes.count} nodes allow provisioning for version: #{version}")
389
+
390
+ if version_nodes.count > 0
391
+
392
+ best_node = version_nodes.max_by { |node| node_score(node) }
393
+
394
+ if best_node && ( allow_over_provisioning || node_score(best_node) > 0 )
395
+ best_node = best_node["id"]
396
+ @logger.debug("[#{service_description}] Provisioning on #{best_node}")
397
+
398
+ prov_req = ProvisionRequest.new
399
+ prov_req.plan = plan
400
+ prov_req.version = version
401
+ # use old credentials to provision a service if provided.
402
+ prov_req.credentials = prov_handle["credentials"] if prov_handle
403
+
404
+ @provision_refs[best_node] += 1
405
+ @nodes[best_node]['available_capacity'] -= @nodes[best_node]['capacity_unit']
406
+ subscription = nil
407
+
408
+ timer = EM.add_timer(@node_timeout) {
409
+ @provision_refs[best_node] -= 1
410
+ @node_nats.unsubscribe(subscription)
411
+ blk.call(timeout_fail)
412
+ }
413
+
414
+ subscription = @node_nats.request("#{service_name}.provision.#{best_node}", prov_req.encode) do |msg|
415
+ @provision_refs[best_node] -= 1
416
+ EM.cancel_timer(timer)
417
+ @node_nats.unsubscribe(subscription)
418
+ response = ProvisionResponse.decode(msg)
419
+
420
+ if response.success
421
+ @logger.debug("Successfully provision response:[#{response.inspect}]")
422
+
423
+ # credentials is not necessary in cache
424
+ prov_req.credentials = nil
425
+ credential = response.credentials
426
+ svc = {:configuration => prov_req.dup, :service_id => credential['name'], :credentials => credential}
427
+ @logger.debug("Provisioned: #{svc.inspect}")
428
+ add_instance_handle(svc)
429
+ blk.call(success(svc))
430
+ else
431
+ blk.call(wrap_error(response))
432
+ end
433
+ end
434
+ else
435
+ # No resources
436
+ @logger.warn("[#{service_description}] Could not find a node to provision")
437
+ blk.call(internal_fail)
438
+ end
439
+ else
440
+ @logger.error("No available nodes supporting version #{version}")
441
+ blk.call(failure(ServiceError.new(ServiceError::UNSUPPORTED_VERSION, version)))
442
+ end
443
+ else
444
+ @logger.error("Unknown plan(#{plan})")
445
+ blk.call(failure(ServiceError.new(ServiceError::UNKNOWN_PLAN, plan)))
446
+ end
447
+ rescue => e
448
+ @logger.warn("Exception at provision_service #{e}")
449
+ blk.call(internal_fail)
450
+ end
451
+
452
+ def bind_instance(instance_id, binding_options, bind_handle=nil, &blk)
453
+ @logger.debug("[#{service_description}] Attempting to bind to service #{instance_id}")
454
+
455
+ begin
456
+ svc = get_instance_handle(instance_id)
457
+ raise ServiceError.new(ServiceError::NOT_FOUND, instance_id) if svc.nil?
458
+
459
+ node_id = svc[:credentials]["node_id"]
460
+ raise "Cannot find node_id for #{instance_id}" if node_id.nil?
461
+
462
+ @logger.debug("[#{service_description}] bind instance #{instance_id} from #{node_id}")
463
+ #FIXME options = {} currently, should parse it in future.
464
+ request = BindRequest.new
465
+ request.name = instance_id
466
+ request.bind_opts = binding_options
467
+ service_id = nil
468
+ if bind_handle
469
+ request.credentials = bind_handle["credentials"]
470
+ service_id = bind_handle["service_id"]
471
+ else
472
+ service_id = SecureRandom.uuid
473
+ end
474
+ subscription = nil
475
+ timer = EM.add_timer(@node_timeout) {
476
+ @node_nats.unsubscribe(subscription)
477
+ blk.call(timeout_fail)
478
+ }
479
+ subscription =
480
+ @node_nats.request( "#{service_name}.bind.#{node_id}",
481
+ request.encode
482
+ ) do |msg|
483
+ EM.cancel_timer(timer)
484
+ @node_nats.unsubscribe(subscription)
485
+ opts = BindResponse.decode(msg)
486
+ if opts.success
487
+ opts = opts.credentials
488
+ # Save binding-options in :data section of configuration
489
+ config = svc[:configuration].clone
490
+ config['data'] ||= {}
491
+ config['data']['binding_options'] = binding_options
492
+ res = {
493
+ :service_id => service_id,
494
+ :configuration => config,
495
+ :credentials => opts
496
+ }
497
+ @logger.debug("[#{service_description}] Binded: #{res.inspect}")
498
+ add_binding_handle(res)
499
+ blk.call(success(res))
500
+ else
501
+ blk.call(wrap_error(opts))
502
+ end
503
+ end
504
+ rescue => e
505
+ if e.instance_of? ServiceError
506
+ blk.call(failure(e))
507
+ else
508
+ @logger.warn("Exception at bind_instance #{e}")
509
+ blk.call(internal_fail)
510
+ end
511
+ end
512
+ end
513
+
514
+ def unbind_instance(instance_id, binding_id, binding_options, &blk)
515
+ @logger.debug("[#{service_description}] Attempting to unbind to service #{instance_id}")
516
+ begin
517
+ svc = get_instance_handle(instance_id)
518
+ raise ServiceError.new(ServiceError::NOT_FOUND, "instance_id #{instance_id}") if svc.nil?
519
+
520
+ handle = get_binding_handle(binding_id)
521
+ raise ServiceError.new(ServiceError::NOT_FOUND, "binding_id #{binding_id}") if handle.nil?
522
+
523
+ node_id = svc[:credentials]["node_id"]
524
+ raise "Cannot find node_id for #{instance_id}" if node_id.nil?
525
+
526
+ @logger.debug("[#{service_description}] Unbind instance #{binding_id} from #{node_id}")
527
+ request = UnbindRequest.new
528
+ request.credentials = handle[:credentials]
529
+
530
+ subscription = nil
531
+ timer = EM.add_timer(@node_timeout) {
532
+ @node_nats.unsubscribe(subscription)
533
+ blk.call(timeout_fail)
534
+ }
535
+ subscription =
536
+ @node_nats.request( "#{service_name}.unbind.#{node_id}",
537
+ request.encode
538
+ ) do |msg|
539
+ delete_binding_handle(handle)
540
+ EM.cancel_timer(timer)
541
+ @node_nats.unsubscribe(subscription)
542
+ opts = SimpleResponse.decode(msg)
543
+ if opts.success
544
+ blk.call(success())
545
+ else
546
+ blk.call(wrap_error(opts))
547
+ end
548
+ end
549
+ rescue => e
550
+ if e.instance_of? ServiceError
551
+ blk.call(failure(e))
552
+ else
553
+ @logger.warn("Exception at unbind_instance #{e}")
554
+ blk.call(internal_fail)
555
+ end
556
+ end
557
+ end
558
+
559
+ def restore_instance(instance_id, backup_path, &blk)
560
+ @logger.debug("[#{service_description}] Attempting to restore to service #{instance_id}")
561
+
562
+ begin
563
+ svc = get_instance_handle(instance_id)
564
+ raise ServiceError.new(ServiceError::NOT_FOUND, instance_id) if svc.nil?
565
+
566
+ node_id = svc[:credentials]["node_id"]
567
+ raise "Cannot find node_id for #{instance_id}" if node_id.nil?
568
+
569
+ @logger.debug("[#{service_description}] restore instance #{instance_id} from #{node_id}")
570
+ request = RestoreRequest.new
571
+ request.instance_id = instance_id
572
+ request.backup_path = backup_path
573
+ subscription = nil
574
+ timer = EM.add_timer(@node_timeout) {
575
+ @node_nats.unsubscribe(subscription)
576
+ blk.call(timeout_fail)
577
+ }
578
+ subscription =
579
+ @node_nats.request( "#{service_name}.restore.#{node_id}",
580
+ request.encode
581
+ ) do |msg|
582
+ EM.cancel_timer(timer)
583
+ @node_nats.unsubscribe(subscription)
584
+ opts = SimpleResponse.decode(msg)
585
+ if opts.success
586
+ blk.call(success())
587
+ else
588
+ blk.call(wrap_error(opts))
589
+ end
590
+ end
591
+ rescue => e
592
+ if e.instance_of? ServiceError
593
+ blk.call(failure(e))
594
+ else
595
+ @logger.warn("Exception at restore_instance #{e}")
596
+ blk.call(internal_fail)
597
+ end
598
+ end
599
+ end
600
+
601
+ # Recover an instance
602
+ # 1) Provision an instance use old credential
603
+ # 2) restore instance use backup file
604
+ # 3) re-bind bindings use old credential
605
+ def recover(instance_id, backup_path, handles, &blk)
606
+ @logger.debug("Recover instance: #{instance_id} from #{backup_path} with #{handles.size} handles.")
607
+ prov_handle, binding_handles = find_instance_handles(instance_id, handles)
608
+ @logger.debug("Provsion handle: #{prov_handle.inspect}. Binding_handles: #{binding_handles.inspect}")
609
+ req = prov_handle["configuration"]
610
+ request = VCAP::Services::Api::GatewayProvisionRequest.new
611
+ request.label = "SERVICENAME-#{req["version"]}" # TODO: TEMPORARY CHANGE UNTIL WE UPDATE vcap_common gem git ref
612
+ request.plan = req["plan"]
613
+ request.version = req["version"]
614
+ provision_service(request, prov_handle) do |msg|
615
+ if msg['success']
616
+ updated_prov_handle = msg['response']
617
+ updated_prov_handle = hash_sym_key_to_str(updated_prov_handle)
618
+ @logger.info("Recover: Success re-provision instance. Updated handle:#{updated_prov_handle}")
619
+ @update_handle_callback.call(updated_prov_handle) do |update_res|
620
+ if not update_res
621
+ @logger.error("Recover: Update provision handle failed.")
622
+ blk.call(internal_fail)
623
+ else
624
+ @logger.info("Recover: Update provision handle success.")
625
+ restore_instance(instance_id, backup_path) do |res|
626
+ if res['success']
627
+ @logger.info("Recover: Success restore instance data.")
628
+ barrier = VCAP::Services::Base::Barrier.new(:timeout => BARRIER_TIMEOUT, :callbacks => binding_handles.length) do |responses|
629
+ @logger.debug("Response from barrier: #{responses}.")
630
+ updated_handles = responses.select{|i| i[0] }
631
+ if updated_handles.length != binding_handles.length
632
+ @logger.error("Recover: re-bind or update handle failed. Expect #{binding_handles.length} successful responses, got #{updated_handles.length} ")
633
+ blk.call(internal_fail)
634
+ else
635
+ @logger.info("Recover: recover instance #{instance_id} complete!")
636
+ result = {
637
+ 'success' => true,
638
+ 'response' => "{}"
639
+ }
640
+ blk.call(result)
641
+ end
642
+ end
643
+ @logger.info("Recover: begin rebind binding handles.")
644
+ bcb = barrier.callback
645
+ binding_handles.each do |handle|
646
+ bind_instance(instance_id, nil, handle) do |bind_res|
647
+ if bind_res['success']
648
+ updated_bind_handle = bind_res['response']
649
+ updated_bind_handle = hash_sym_key_to_str(updated_bind_handle)
650
+ @logger.info("Recover: success re-bind binding: #{updated_bind_handle}")
651
+ @update_handle_callback.call(updated_bind_handle) do |update_response|
652
+ if update_response
653
+ @logger.info("Recover: success to update handle: #{updated_prov_handle}")
654
+ bcb.call(updated_bind_handle)
655
+ else
656
+ @logger.error("Recover: failed to update handle: #{updated_prov_handle}")
657
+ bcb.call(false)
658
+ end
659
+ end
660
+ else
661
+ @logger.error("Recover: failed to re-bind binding handle: #{handle}")
662
+ bcb.call(false)
663
+ end
664
+ end
665
+ end
666
+ else
667
+ @logger.error("Recover: failed to restore instance: #{instance_id}.")
668
+ blk.call(internal_fail)
669
+ end
670
+ end
671
+ end
672
+ end
673
+ else
674
+ @logger.error("Recover: failed to re-provision instance. Handle: #{prov_handle}.")
675
+ blk.call(internal_fail)
676
+ end
677
+ end
678
+ rescue => e
679
+ @logger.warn("Exception at recover #{e}")
680
+ blk.call(internal_fail)
681
+ end
682
+
683
+ def migrate_instance(node_id, instance_id, action, &blk)
684
+ @logger.debug("[#{service_description}] Attempting to #{action} instance #{instance_id} in node #{node_id}")
685
+
686
+ begin
687
+ svc = get_instance_handle(instance_id)
688
+ raise ServiceError.new(ServiceError::NOT_FOUND, instance_id) if svc.nil?
689
+
690
+ binding_handles = find_instance_bindings(instance_id)
691
+ subscription = nil
692
+ message = nil
693
+ channel = nil
694
+ if action == "disable" || action == "enable" || action == "import" || action == "update" || action == "cleanupnfs"
695
+ channel = "#{service_name}.#{action}_instance.#{node_id}"
696
+ message = Yajl::Encoder.encode([svc, binding_handles])
697
+ elsif action == "unprovision"
698
+ channel = "#{service_name}.unprovision.#{node_id}"
699
+ bindings = find_instance_bindings(instance_id)
700
+ request = UnprovisionRequest.new
701
+ request.name = instance_id
702
+ request.bindings = bindings.map{|h| h[:credentials]}
703
+ message = request.encode
704
+ elsif action == "check"
705
+ if node_id == svc[:credentials]["node_id"]
706
+ blk.call(success())
707
+ return
708
+ else
709
+ raise ServiceError.new(ServiceError::NOT_FOUND, instance_id)
710
+ end
711
+ else
712
+ raise ServiceError.new(ServiceError::NOT_FOUND, action)
713
+ end
714
+ timer = EM.add_timer(@node_timeout) {
715
+ @node_nats.unsubscribe(subscription)
716
+ blk.call(timeout_fail)
717
+ }
718
+ subscription = @node_nats.request(channel, message) do |msg|
719
+ EM.cancel_timer(timer)
720
+ @node_nats.unsubscribe(subscription)
721
+ if action != "update"
722
+ response = SimpleResponse.decode(msg)
723
+ if response.success
724
+ blk.call(success())
725
+ else
726
+ blk.call(wrap_error(response))
727
+ end
728
+ else
729
+ handles = Yajl::Parser.parse(msg)
730
+ handles.each do |handle|
731
+ @update_handle_callback.call(handle) do |update_res|
732
+ if update_res
733
+ @logger.info("Migration: success to update handle: #{handle}")
734
+ else
735
+ @logger.error("Migration: failed to update handle: #{handle}")
736
+ blk.call(wrap_error(response))
737
+ end
738
+ end
739
+ blk.call(success())
740
+ end
741
+ end
742
+ end
743
+ rescue => e
744
+ if e.instance_of? ServiceError
745
+ blk.call(failure(e))
746
+ else
747
+ @logger.warn("Exception at migrate_instance #{e}")
748
+ blk.call(internal_fail)
749
+ end
750
+ end
751
+ end
752
+
753
+ # Snapshot apis filter
754
+ def before_snapshot_apis service_id, *args, &blk
755
+ raise "service_id can't be nil" unless service_id
756
+
757
+ svc = get_instance_handle(service_id)
758
+ raise ServiceError.new(ServiceError::NOT_FOUND, service_id) unless svc
759
+
760
+ plan = find_service_plan(svc)
761
+ extensions_enabled?(plan, :snapshot, &blk)
762
+ rescue => e
763
+ handle_error(e, &blk)
764
+ nil # terminate evoke chain
765
+ end
766
+
767
+ def find_service_plan(svc)
768
+ config = svc[:configuration] || svc["configuration"]
769
+ raise "Can't find configuration for service=#{service_id} #{svc.inspect}" unless config
770
+ plan = config["plan"] || config[:plan]
771
+ raise "Can't find plan for service=#{service_id} #{svc.inspect}" unless plan
772
+ plan
773
+ end
774
+
775
+ # Serialization apis filter
776
+ def before_serialization_apis service_id, *args, &blk
777
+ raise "service_id can't be nil" unless service_id
778
+
779
+ svc = get_instance_handle(service_id)
780
+ raise ServiceError.new(ServiceError::NOT_FOUND, service_id) unless svc
781
+
782
+ plan = find_service_plan(svc)
783
+ extensions_enabled?(plan, :serialization, &blk)
784
+ rescue => e
785
+ handle_error(e, &blk)
786
+ nil # terminate evoke chain
787
+ end
788
+
789
+ def before_job_apis service_id, *args, &blk
790
+ raise "service_id can't be nil" unless service_id
791
+
792
+ svc = get_instance_handle(service_id)
793
+ raise ServiceError.new(ServiceError::NOT_FOUND, service_id) unless svc
794
+
795
+ plan = find_service_plan(svc)
796
+ extensions_enabled?(plan, :job, &blk)
797
+ rescue => e
798
+ handle_error(e, &blk)
799
+ nil # terminate evoke chain
800
+ end
801
+
802
+ def extensions_enabled?(plan, extension, &blk)
803
+ unless (@extensions[plan.to_sym] && @extensions[plan.to_sym][extension.to_sym])
804
+ @logger.warn("Extension #{extension} is not enabled for plan #{plan}")
805
+ blk.call(failure(ServiceError.new(ServiceError::EXTENSION_NOT_IMPL, extension)))
806
+ nil
807
+ else
808
+ true
809
+ end
810
+ end
811
+
812
+ def snapshot_metadata(service_id)
813
+ service = self.options[:service]
814
+ instance = get_instance_handle(service_id)
815
+
816
+ metadata = {
817
+ :plan => find_service_plan(instance),
818
+ :provider => service[:provider] || 'core',
819
+ :service_version => instance[:configuration][:version],
820
+ }
821
+ metadata
822
+ rescue => e
823
+ @logger.warn("Failed to get snapshot_metadata #{e}.")
824
+ end
825
+
826
+ # Create a create_snapshot job and return the job object.
827
+ #
828
+ def create_snapshot(service_id, &blk)
829
+ @logger.debug("Create snapshot job for service_id=#{service_id}")
830
+ job_id = create_snapshot_job.create(:service_id => service_id,
831
+ :node_id => find_node(service_id),
832
+ :metadata=> snapshot_metadata(service_id),
833
+ )
834
+ job = get_job(job_id)
835
+ @logger.info("CreateSnapshotJob created: #{job.inspect}")
836
+ blk.call(success(job))
837
+ rescue => e
838
+ handle_error(e, &blk)
839
+ end
840
+
841
+ # Get detail job information by job id.
842
+ #
843
+ def job_details(service_id, job_id, &blk)
844
+ @logger.debug("Get job_id=#{job_id} for service_id=#{service_id}")
845
+ svc = get_instance_handle(service_id)
846
+ raise ServiceError.new(ServiceError::NOT_FOUND, service_id) unless svc
847
+ job = get_job(job_id)
848
+ raise ServiceError.new(ServiceError::NOT_FOUND, job_id) unless job
849
+ blk.call(success(job))
850
+ rescue => e
851
+ handle_error(e, &blk)
852
+ end
853
+
854
+ # Get detail snapshot information
855
+ #
856
+ def get_snapshot(service_id, snapshot_id, &blk)
857
+ @logger.debug("Get snapshot_id=#{snapshot_id} for service_id=#{service_id}")
858
+ svc = get_instance_handle(service_id)
859
+ raise ServiceError.new(ServiceError::NOT_FOUND, service_id) unless svc
860
+ snapshot = snapshot_details(service_id, snapshot_id)
861
+ raise ServiceError.new(ServiceError::NOT_FOUND, snapshot_id) unless snapshot
862
+ blk.call(success(filter_keys(snapshot)))
863
+ rescue => e
864
+ handle_error(e, &blk)
865
+ end
866
+
867
+ # Update the name of a snapshot
868
+ def update_snapshot_name(service_id, snapshot_id, name, &blk)
869
+ @logger.debug("Update name of snapshot=#{snapshot_id} for service_id=#{service_id} to '#{name}'")
870
+ svc = get_instance_handle(service_id)
871
+ raise ServiceError.new(ServiceError::NOT_FOUND, service_id) unless svc
872
+
873
+ update_name(service_id, snapshot_id, name)
874
+ blk.call(success())
875
+ rescue => e
876
+ handle_error(e, &blk)
877
+ end
878
+
879
+ # Get all snapshots related to an instance
880
+ #
881
+ def enumerate_snapshots(service_id, &blk)
882
+ @logger.debug("Get snapshots for service_id=#{service_id}")
883
+ svc = get_instance_handle(service_id)
884
+ raise ServiceError.new(ServiceError::NOT_FOUND, service_id) unless svc
885
+ snapshots = service_snapshots(service_id)
886
+ res = snapshots.map{|s| filter_keys(s)}
887
+ blk.call(success({:snapshots => res }))
888
+ rescue => e
889
+ handle_error(e, &blk)
890
+ end
891
+
892
+ def rollback_snapshot(service_id, snapshot_id, &blk)
893
+ @logger.debug("Rollback snapshot=#{snapshot_id} for service_id=#{service_id}")
894
+ svc = get_instance_handle(service_id)
895
+ raise ServiceError.new(ServiceError::NOT_FOUND, service_id) unless svc
896
+ snapshot = snapshot_details(service_id, snapshot_id)
897
+ raise ServiceError.new(ServiceError::NOT_FOUND, snapshot_id) unless snapshot
898
+ job_id = rollback_snapshot_job.create(:service_id => service_id, :snapshot_id => snapshot_id,
899
+ :node_id => find_node(service_id))
900
+ job = get_job(job_id)
901
+ @logger.info("RoallbackSnapshotJob created: #{job.inspect}")
902
+ blk.call(success(job))
903
+ rescue => e
904
+ handle_error(e, &blk)
905
+ end
906
+
907
+ def delete_snapshot(service_id, snapshot_id, &blk)
908
+ @logger.debug("Delete snapshot=#{snapshot_id} for service_id=#{service_id}")
909
+ snapshot = snapshot_details(service_id, snapshot_id)
910
+ raise ServiceError.new(ServiceError::NOT_FOUND, snapshot_id) unless snapshot
911
+ job_id = delete_snapshot_job.create(:service_id => service_id, :snapshot_id => snapshot_id, :node_id => find_node(service_id))
912
+ job = get_job(job_id)
913
+ @logger.info("DeleteSnapshotJob created: #{job.inspect}")
914
+ blk.call(success(job))
915
+ rescue => e
916
+ handle_error(e, &blk)
917
+ end
918
+
919
+ def create_serialized_url(service_id, snapshot_id, &blk)
920
+ @logger.debug("create serialized url for snapshot=#{snapshot_id} of service_id=#{service_id}")
921
+ snapshot = snapshot_details(service_id, snapshot_id)
922
+ raise ServiceError.new(ServiceError::NOT_FOUND, snapshot_id) unless snapshot
923
+ job_id = create_serialized_url_job.create(:service_id => service_id, :node_id => find_node(service_id), :snapshot_id => snapshot_id)
924
+ job = get_job(job_id)
925
+ blk.call(success(job))
926
+ rescue => e
927
+ handle_error(e, &blk)
928
+ end
929
+
930
+ # Genereate the serialized URL of service snapshot.
931
+ # Return NOTFOUND error if no download token is associate with service instance.
932
+ def get_serialized_url(service_id, snapshot_id, &blk)
933
+ @logger.debug("get serialized url for snapshot=#{snapshot_id} of service_id=#{service_id}")
934
+ snapshot = snapshot_details(service_id, snapshot_id)
935
+ raise ServiceError.new(ServiceError::NOT_FOUND, snapshot_id) unless snapshot
936
+ token = snapshot["token"]
937
+ raise ServiceError.new(ServiceError::NOT_FOUND, "Download url for service_id=#{service_id}, snapshot=#{snapshot_id}") unless token
938
+
939
+ url_template = self.options[:download_url_template]
940
+ service = self.options[:service][:name]
941
+ raise "Configuration error, can't find download_url_template" unless url_template
942
+ raise "Configuration error, can't find service name." unless service
943
+ url = url_template % {:service => service, :name => service_id, :snapshot_id => snapshot_id, :token => token}
944
+ blk.call(success({:url => url}))
945
+ rescue => e
946
+ handle_error(e, &blk)
947
+ end
948
+
949
+ def import_from_url(service_id, url, &blk)
950
+ @logger.debug("import serialized data from url:#{url} for service_id=#{service_id}")
951
+ job_id = import_from_url_job.create(:service_id => service_id, :url => url, :node_id => find_node(service_id))
952
+ job = get_job(job_id)
953
+ blk.call(success(job))
954
+ rescue => e
955
+ wrap_error(e, &blk)
956
+ end
957
+
958
+ # convert symbol key to string key
959
+ def hash_sym_key_to_str(hash)
960
+ new_hash = {}
961
+ hash.each do |k, v|
962
+ if v.is_a? Hash
963
+ v = hash_sym_key_to_str(v)
964
+ end
965
+ if k.is_a? Symbol
966
+ new_hash[k.to_s] = v
967
+ else
968
+ new_hash[k] = v
969
+ end
970
+ end
971
+ return new_hash
972
+ end
973
+
974
+ def on_update_service_handle(msg, reply)
975
+ @logger.debug("[#{service_description}] Update service handle #{msg.inspect}")
976
+ handle = Yajl::Parser.parse(msg)
977
+ @update_handle_callback.call(handle) do |response|
978
+ response = Yajl::Encoder.encode(response)
979
+ @node_nats.publish(reply, response)
980
+ end
981
+ end
982
+
983
+ # Gateway invoke this function to register a block which provisioner could use to update a service handle
984
+ def register_update_handle_callback(&blk)
985
+ @logger.debug("Register update handle callback with #{blk}")
986
+ @update_handle_callback = blk
987
+ end
988
+
989
+ def varz_details
990
+ # Service Provisioner subclasses may want to override this method
991
+ # to provide service specific data beyond the following
992
+
993
+ # Mask password from varz details
994
+ svcs = get_all_handles
995
+ svcs.each do |k,v|
996
+ v[:credentials]['pass'] &&= MASKED_PASSWORD
997
+ v[:credentials]['password'] &&= MASKED_PASSWORD
998
+ end
999
+
1000
+ orphan_instances = @final_orphan_instances.deep_dup
1001
+ orphan_bindings = @final_orphan_bindings.deep_dup
1002
+ orphan_bindings.each do |k, list|
1003
+ list.each do |v|
1004
+ v['pass'] &&= MASKED_PASSWORD
1005
+ v['password'] &&= MASKED_PASSWORD
1006
+ end
1007
+ end
1008
+
1009
+ plan_mgmt = []
1010
+ @plan_mgmt.each do |plan, v|
1011
+ plan_nodes = @nodes.select { |_, node| node["plan"] == plan.to_s }.values
1012
+ score = plan_nodes.inject(0) { |sum, node| sum + node_score(node) }
1013
+ plan_mgmt << {
1014
+ :plan => plan,
1015
+ :score => score,
1016
+ :low_water => v[:low_water],
1017
+ :high_water => v[:high_water],
1018
+ :allow_over_provisioning => v[:allow_over_provisioning]?1:0
1019
+ }
1020
+ end
1021
+
1022
+ varz = {
1023
+ :nodes => @nodes,
1024
+ :prov_svcs => svcs,
1025
+ :orphan_instances => orphan_instances,
1026
+ :orphan_bindings => orphan_bindings,
1027
+ :plans => plan_mgmt,
1028
+ :responses_metrics => @responses_metrics,
1029
+ }
1030
+ return varz
1031
+ rescue => e
1032
+ @logger.warn("Exception at varz_details #{e}")
1033
+ end
1034
+
1035
+ ########
1036
+ # Helpers
1037
+ ########
1038
+
1039
+ # Find instance related handles in all handles
1040
+ def find_instance_handles(instance_id, handles)
1041
+ prov_handle = nil
1042
+ binding_handles = []
1043
+ handles.each do |h|
1044
+ if h['service_id'] == instance_id
1045
+ prov_handle = h
1046
+ else
1047
+ binding_handles << h if h['credentials']['name'] == instance_id
1048
+ end
1049
+ end
1050
+ return [prov_handle, binding_handles]
1051
+ end
1052
+
1053
+ # wrap a service message to hash
1054
+ def wrap_error(service_msg)
1055
+ {
1056
+ 'success' => false,
1057
+ 'response' => service_msg.error
1058
+ }
1059
+ end
1060
+
1061
+ # handle request exception
1062
+ def handle_error(e, &blk)
1063
+ @logger.error("[#{service_description}] Unexpected Error: #{e}:[#{e.backtrace.join(" | ")}]")
1064
+ if e.instance_of? ServiceError
1065
+ blk.call(failure(e))
1066
+ else
1067
+ blk.call(internal_fail)
1068
+ end
1069
+ end
1070
+
1071
+ # Find which node the service instance is running on.
1072
+ def find_node(instance_id)
1073
+ svc = get_instance_handle(instance_id)
1074
+ raise ServiceError.new(ServiceError::NOT_FOUND, "instance_id #{instance_id}") if svc.nil?
1075
+ node_id = svc[:credentials]["node_id"]
1076
+ raise "Cannot find node_id for #{instance_id}" if node_id.nil?
1077
+ node_id
1078
+ end
1079
+
1080
+ # node_score(node) -> number. this base class provisions on the
1081
+ # "best" node (lowest load, most free capacity, etc). this method
1082
+ # should return a number; higher scores represent "better" nodes;
1083
+ # negative/zero scores mean that a node should be ignored
1084
+ def node_score(node)
1085
+ node['available_capacity'] if node
1086
+ end
1087
+
1088
+ # Service Provisioner subclasses must implement the following
1089
+ # methods
1090
+
1091
+ # service_name() --> string
1092
+ # (inhereted from VCAP::Services::Base::Base)
1093
+ #
1094
+
1095
+ # Snapshot v2 API
1096
+
1097
+ def create_snapshot_v2(service_id, name, &blk)
1098
+ snapshot = snapshot_client.create_empty_snapshot(service_id, name)
1099
+ blk.call(success(snapshot))
1100
+ rescue => e
1101
+ handle_error(e, &blk)
1102
+ end
1103
+
1104
+ def enumerate_snapshots_v2(service_id, &blk)
1105
+ snapshots = snapshot_client.service_snapshots(service_id)
1106
+ blk.call(success(snapshots))
1107
+ rescue => e
1108
+ handle_error(e, &blk)
1109
+ end
1110
+
1111
+ # various lifecycle jobs class
1112
+ abstract :create_snapshot_job, :rollback_snapshot_job, :delete_snapshot_job, :create_serialized_url_job, :import_from_url_job
1113
+
1114
+ # register before filter
1115
+ before [:create_snapshot, :get_snapshot, :enumerate_snapshots, :delete_snapshot, :rollback_snapshot, :update_snapshot_name, :enumerate_snapshots_v2, :create_snapshot_v2], :before_snapshot_apis
1116
+
1117
+ before [:create_serialized_url, :get_serialized_url, :import_from_url], :before_serialization_apis
1118
+
1119
+ before :job_details, :before_job_apis
1120
+ end