vcap_services_base 0.2.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/base/abstract.rb +11 -0
- data/lib/base/api/message.rb +31 -0
- data/lib/base/asynchronous_service_gateway.rb +529 -0
- data/lib/base/backup.rb +206 -0
- data/lib/base/barrier.rb +54 -0
- data/lib/base/base.rb +159 -0
- data/lib/base/base_async_gateway.rb +164 -0
- data/lib/base/base_job.rb +5 -0
- data/lib/base/catalog_manager_base.rb +67 -0
- data/lib/base/catalog_manager_v1.rb +225 -0
- data/lib/base/catalog_manager_v2.rb +291 -0
- data/lib/base/cloud_controller_services.rb +75 -0
- data/lib/base/datamapper_l.rb +148 -0
- data/lib/base/gateway.rb +167 -0
- data/lib/base/gateway_service_catalog.rb +68 -0
- data/lib/base/http_handler.rb +101 -0
- data/lib/base/job/async_job.rb +71 -0
- data/lib/base/job/config.rb +27 -0
- data/lib/base/job/lock.rb +153 -0
- data/lib/base/job/package.rb +112 -0
- data/lib/base/job/serialization.rb +365 -0
- data/lib/base/job/snapshot.rb +354 -0
- data/lib/base/node.rb +471 -0
- data/lib/base/node_bin.rb +154 -0
- data/lib/base/plan.rb +63 -0
- data/lib/base/provisioner.rb +1120 -0
- data/lib/base/provisioner_v1.rb +125 -0
- data/lib/base/provisioner_v2.rb +193 -0
- data/lib/base/service.rb +93 -0
- data/lib/base/service_advertiser.rb +184 -0
- data/lib/base/service_error.rb +122 -0
- data/lib/base/service_message.rb +94 -0
- data/lib/base/service_plan_change_set.rb +11 -0
- data/lib/base/simple_aop.rb +63 -0
- data/lib/base/snapshot_v2/snapshot.rb +227 -0
- data/lib/base/snapshot_v2/snapshot_client.rb +158 -0
- data/lib/base/snapshot_v2/snapshot_job.rb +95 -0
- data/lib/base/utils.rb +63 -0
- data/lib/base/version.rb +7 -0
- data/lib/base/warden/instance_utils.rb +161 -0
- data/lib/base/warden/node_utils.rb +205 -0
- data/lib/base/warden/service.rb +426 -0
- data/lib/base/worker_bin.rb +76 -0
- data/lib/vcap_services_base.rb +16 -0
- 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
|
data/lib/base/plan.rb
ADDED
@@ -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
|