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