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