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,101 @@
|
|
1
|
+
require 'uaa'
|
2
|
+
|
3
|
+
class HTTPHandler
|
4
|
+
HTTP_UNAUTHENTICATED_CODE = 401
|
5
|
+
attr_reader :logger
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
@logger = options[:logger]
|
9
|
+
@options = options
|
10
|
+
@cld_ctrl_uri = options[:cloud_controller_uri]
|
11
|
+
end
|
12
|
+
|
13
|
+
def cc_http_request(args, &block)
|
14
|
+
args[:uri] = "#{@cld_ctrl_uri}#{args[:uri]}"
|
15
|
+
args[:head] = cc_req_hdrs
|
16
|
+
|
17
|
+
max_attempts = args[:max_attempts] || 2
|
18
|
+
attempts = 0
|
19
|
+
while true
|
20
|
+
attempts += 1
|
21
|
+
logger.debug("#{args[:method].upcase} - #{args[:uri]}")
|
22
|
+
http = make_http_request(args)
|
23
|
+
if attempts < max_attempts && http.response_header.status == HTTP_UNAUTHENTICATED_CODE
|
24
|
+
args[:head] = refresh_client_auth_token
|
25
|
+
else
|
26
|
+
block.call(http)
|
27
|
+
return http
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def refresh_client_auth_token
|
33
|
+
# Load the auth token to be sent out in Authorization header when making CCNG-v2 requests
|
34
|
+
credentials = @options.fetch(:uaa_client_auth_credentials)
|
35
|
+
client_id = @options.fetch(:uaa_client_id)
|
36
|
+
|
37
|
+
if ENV["AUTHORIZATION_TOKEN"]
|
38
|
+
uaa_client_auth_token = ENV["AUTHORIZATION_TOKEN"]
|
39
|
+
else
|
40
|
+
ti = CF::UAA::TokenIssuer.new(@options.fetch(:uaa_endpoint), client_id)
|
41
|
+
token = ti.implicit_grant_with_creds(credentials).info
|
42
|
+
uaa_client_auth_token = "#{token["token_type"]} #{token["access_token"]}"
|
43
|
+
expire_time = token["expires_in"].to_i
|
44
|
+
logger.info("Successfully refresh auth token for:\
|
45
|
+
#{credentials[:username]}, token expires in \
|
46
|
+
#{expire_time} seconds.")
|
47
|
+
end
|
48
|
+
|
49
|
+
@cc_req_hdrs = {
|
50
|
+
'Content-Type' => 'application/json',
|
51
|
+
'Authorization' => uaa_client_auth_token
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def cc_req_hdrs
|
56
|
+
@cc_req_hdrs || refresh_client_auth_token
|
57
|
+
end
|
58
|
+
|
59
|
+
def generate_cc_advertise_offering_request(service, active = true)
|
60
|
+
svc = service.to_hash
|
61
|
+
|
62
|
+
# NOTE: In CCNG, multiple versions is expected to be supported via multiple plans
|
63
|
+
# The gateway will have to maintain a mapping of plan-name to version so that
|
64
|
+
# the correct version will be provisioned
|
65
|
+
plans = {}
|
66
|
+
svc.delete('plans').each do |p|
|
67
|
+
plans[p[:name]] = {
|
68
|
+
"unique_id" => p[:unique_id],
|
69
|
+
"name" => p[:name],
|
70
|
+
"description" => p[:description],
|
71
|
+
"free" => p[:free]
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
[svc, plans]
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def make_http_request(args)
|
80
|
+
req = {
|
81
|
+
:head => args[:head],
|
82
|
+
:body => args[:body],
|
83
|
+
}
|
84
|
+
|
85
|
+
f = Fiber.current
|
86
|
+
http = EM::HttpRequest.new(args[:uri]).send(args[:method], req)
|
87
|
+
if http.error && http.error != ""
|
88
|
+
unless args[:need_raise]
|
89
|
+
@logger.error("CC Catalog Manager: Failed to connect to CC, the error is #{http.error}")
|
90
|
+
return
|
91
|
+
else
|
92
|
+
raise("CC Catalog Manager: Failed to connect to CC, the error is #{http.error}")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
http.callback { f.resume(http, nil) }
|
96
|
+
http.errback { |e| f.resume(http, e) }
|
97
|
+
_, error = Fiber.yield
|
98
|
+
yield http, error if block_given?
|
99
|
+
http
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
require "resque-status"
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
5
|
+
require "config"
|
6
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..')
|
7
|
+
require "service_error"
|
8
|
+
|
9
|
+
module Resque
|
10
|
+
extend self
|
11
|
+
# Patch Resque so we can determine queue by input args.
|
12
|
+
# Job class can define select_queue method and the result will be the queue name.
|
13
|
+
def enqueue(klass, *args)
|
14
|
+
queue = (klass.respond_to?(:select_queue) && klass.select_queue(*args)) || queue_from_class(klass)
|
15
|
+
enqueue_to(queue, klass, *args)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
module Resque::Plugins::Status
|
21
|
+
class Hash
|
22
|
+
# new attributes
|
23
|
+
hash_accessor :complete_time
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# A thin layer wraps resque-status
|
28
|
+
module VCAP::Services::Base::AsyncJob
|
29
|
+
include VCAP::Services::Base::Error
|
30
|
+
|
31
|
+
def job_repo_setup
|
32
|
+
redis = Config.redis
|
33
|
+
@logger = Config.logger
|
34
|
+
raise "AsyncJob requires redis configuration." unless redis
|
35
|
+
@logger.debug("Initialize Resque using #{redis}") if @logger
|
36
|
+
::Resque.redis = redis
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_job(jobid)
|
40
|
+
res = Resque::Plugins::Status::Hash.get(jobid)
|
41
|
+
job_to_json(res)
|
42
|
+
end
|
43
|
+
|
44
|
+
def remove_job(jobid)
|
45
|
+
Resque::Plugins::Status::Hash.remove(jobid)
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_all_jobs()
|
49
|
+
Resque::Plugins::Status::Hash.status_ids
|
50
|
+
end
|
51
|
+
|
52
|
+
def job_to_json(job)
|
53
|
+
return nil unless job
|
54
|
+
res = {
|
55
|
+
:job_id => job.uuid,
|
56
|
+
:status => job.status,
|
57
|
+
:start_time => job.time.to_s,
|
58
|
+
:description => job.options[:description] || "None"
|
59
|
+
}
|
60
|
+
res[:complete_time] = job.complete_time if job.complete_time
|
61
|
+
res[:result] = validate_message(job.message) if job.message
|
62
|
+
res
|
63
|
+
end
|
64
|
+
|
65
|
+
def validate_message(msg)
|
66
|
+
Yajl::Parser.parse(msg)
|
67
|
+
rescue => e
|
68
|
+
# generate internal error if we can't parse err msg
|
69
|
+
ServiceError.new(ServiceError::INTERNAL_ERROR).to_hash
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
require "redis"
|
3
|
+
require "resque"
|
4
|
+
|
5
|
+
module VCAP
|
6
|
+
module Services
|
7
|
+
module Base
|
8
|
+
module AsyncJob
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class VCAP::Services::Base::AsyncJob::Config
|
15
|
+
class << self
|
16
|
+
attr_reader :redis_config, :redis, :logger
|
17
|
+
def redis_config=(config)
|
18
|
+
@redis_config = config
|
19
|
+
@redis = ::Redis.new config
|
20
|
+
Resque.redis = @redis
|
21
|
+
end
|
22
|
+
|
23
|
+
def logger=(logger)
|
24
|
+
@logger = logger
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# Copyright (c) 2009-2011 VMware, Inc.
|
2
|
+
require "logger"
|
3
|
+
require "redis"
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
6
|
+
require "config"
|
7
|
+
|
8
|
+
# redis locking primitive using setnx.
|
9
|
+
# http://redis.io/commands/setnx
|
10
|
+
module VCAP::Services::Base::AsyncJob
|
11
|
+
class Lock
|
12
|
+
attr_reader :expiration, :timeout, :name
|
13
|
+
include VCAP::Services::Base::Error
|
14
|
+
|
15
|
+
# Options for lock
|
16
|
+
# name - The uuid of the lock
|
17
|
+
# timeout - The time that waits to acquire the lock, default 20 seconds
|
18
|
+
# expiration - Lock expires in given seconds if not refreshed, default 10 seconds
|
19
|
+
# logger - The logger..
|
20
|
+
# ttl - The max time that a thread can acquire the lock, default 600 seconds. Lock raise +JOB_TIMEOUT+ error once the ttl is exceeded.
|
21
|
+
def initialize(name, opts={})
|
22
|
+
@name = name
|
23
|
+
@timeout = opts[:timeout] || 20 #seconds
|
24
|
+
@expiration = opts[:expiration] || 10 # seconds
|
25
|
+
@ttl = opts[:ttl] || 600 # seconds
|
26
|
+
@logger = opts[:logger] || make_logger
|
27
|
+
config = Config.redis_config
|
28
|
+
raise "Can't find configuration of redis." unless config
|
29
|
+
@redis = ::Redis.new(config)
|
30
|
+
@released_thread = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def make_logger
|
34
|
+
logger = Logger.new(STDOUT)
|
35
|
+
logger.level = Logger::ERROR
|
36
|
+
logger
|
37
|
+
end
|
38
|
+
|
39
|
+
def lock
|
40
|
+
@logger.debug("Acquiring lock: #{@name}")
|
41
|
+
started = Time.now.to_f
|
42
|
+
expiration = started.to_f + @expiration + 1
|
43
|
+
until @redis.setnx(@name, expiration)
|
44
|
+
existing_lock = @redis.get(@name)
|
45
|
+
if existing_lock.to_f < Time.now.to_f
|
46
|
+
@logger.debug("Lock #{@name} is expired, trying to acquire it.")
|
47
|
+
break if watch_and_update(@redis, expiration)
|
48
|
+
end
|
49
|
+
|
50
|
+
raise ServiceError.new(ServiceError::JOB_QUEUE_TIMEOUT, @timeout)if Time.now.to_f - started > @timeout
|
51
|
+
|
52
|
+
sleep(1)
|
53
|
+
|
54
|
+
expiration = Time.now.to_f + @expiration + 1
|
55
|
+
end
|
56
|
+
|
57
|
+
@lock_expiration = expiration
|
58
|
+
refresh_thread = setup_refresh_thread
|
59
|
+
@logger.debug("Lock #{@name} is acquired, will expire at #{@lock_expiration}")
|
60
|
+
|
61
|
+
begin
|
62
|
+
Timeout::timeout(@ttl) do
|
63
|
+
yield if block_given?
|
64
|
+
end
|
65
|
+
rescue Timeout::Error =>e
|
66
|
+
raise ServiceError.new(ServiceError::JOB_TIMEOUT, @ttl)
|
67
|
+
ensure
|
68
|
+
release_thread(refresh_thread)
|
69
|
+
delete
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def watch_and_update(redis, expiration)
|
74
|
+
redis.watch(@name)
|
75
|
+
res = redis.multi do
|
76
|
+
redis.set(@name, expiration)
|
77
|
+
end
|
78
|
+
if res
|
79
|
+
@logger.debug("Lock #{@name} is renewed and acquired.")
|
80
|
+
else
|
81
|
+
@logger.debug("Lock #{@name} was updated by others.")
|
82
|
+
end
|
83
|
+
res
|
84
|
+
end
|
85
|
+
|
86
|
+
def release_thread t
|
87
|
+
# gracefully terminate refresh thread.
|
88
|
+
@released_thread[t.object_id] = true
|
89
|
+
waited = 0
|
90
|
+
while (waited += 1) <= 5
|
91
|
+
# thread is terminated when t.status == nil or false
|
92
|
+
return unless t.status
|
93
|
+
sleep 1
|
94
|
+
end
|
95
|
+
# force terminate after wait 5 seconds.
|
96
|
+
t.exit
|
97
|
+
end
|
98
|
+
|
99
|
+
def released?
|
100
|
+
@released_thread[Thread.current.object_id]
|
101
|
+
end
|
102
|
+
|
103
|
+
def setup_refresh_thread
|
104
|
+
t = Thread.new do
|
105
|
+
redis = ::Redis.new(Config.redis_config)
|
106
|
+
sleep_interval = [1.0, @expiration/2].max.to_i
|
107
|
+
begin
|
108
|
+
while not released? do
|
109
|
+
@logger.debug("Renewing lock #{@name}")
|
110
|
+
redis.watch(@name)
|
111
|
+
existing_lock = redis.get(@name)
|
112
|
+
|
113
|
+
break if existing_lock.to_f > @lock_expiration # lock has been updated by others
|
114
|
+
expiration = Time.now.to_f + @expiration + 1
|
115
|
+
break unless watch_and_update(redis, expiration)
|
116
|
+
@lock_expiration = expiration
|
117
|
+
sleep_interval.times do
|
118
|
+
sleep 1
|
119
|
+
break if released?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
rescue => e
|
123
|
+
@logger.error("Can't renew lock #{@name}, #{e}")
|
124
|
+
ensure
|
125
|
+
begin
|
126
|
+
@logger.debug("Lock renew thread for #{@name} exited.")
|
127
|
+
redis.quit
|
128
|
+
rescue => e
|
129
|
+
# just logging, ignore error
|
130
|
+
@logger.debug("Ignore error when quit: #{e}")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
t
|
135
|
+
end
|
136
|
+
|
137
|
+
def delete
|
138
|
+
@logger.debug("Deleting lock: #{@name}")
|
139
|
+
existing_lock = @redis.get(@name)
|
140
|
+
@logger.debug("Lock #{@name} is acquired by others.")if existing_lock.to_f > @lock_expiration
|
141
|
+
@redis.watch(@name)
|
142
|
+
res = @redis.multi do
|
143
|
+
@redis.del(@name)
|
144
|
+
end
|
145
|
+
if res
|
146
|
+
@logger.debug("Lock #{@name} is deleted.")
|
147
|
+
else
|
148
|
+
@logger.debug("Lock #{@name} is acquired by others.")
|
149
|
+
end
|
150
|
+
true
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
require "zip/zip"
|
3
|
+
require "yajl"
|
4
|
+
require 'vcap/common'
|
5
|
+
|
6
|
+
module VCAP::Services::Base::AsyncJob
|
7
|
+
|
8
|
+
class Package
|
9
|
+
include VCAP::Services::Base::Error
|
10
|
+
|
11
|
+
MANIFEST_FILE = "manifest".freeze
|
12
|
+
CONTENT_FOLDER = "content".freeze
|
13
|
+
attr_reader :manifest
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def load path
|
17
|
+
raise "File #{path} not exists." unless File.exists? path
|
18
|
+
p = new(path)
|
19
|
+
p.load_manifest
|
20
|
+
p
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(zipfile, opts={})
|
25
|
+
@zipfile = zipfile
|
26
|
+
@files = {}
|
27
|
+
@manifest = {}
|
28
|
+
@filemode = opts[:mode] || 0644
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_files(files)
|
32
|
+
files = Array(files)
|
33
|
+
files.each do |file|
|
34
|
+
raise "File #{file} not found." unless File.exists? file
|
35
|
+
raise "File #{file} is not readable." unless File.readable? file
|
36
|
+
basename = File.basename file
|
37
|
+
@files[basename] = file
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# add +hash+ to manifest file.
|
42
|
+
def manifest=(hash)
|
43
|
+
return unless hash
|
44
|
+
raise "Input should be Hash" unless hash.is_a? Hash
|
45
|
+
@manifest.merge! VCAP.symbolize_keys(hash)
|
46
|
+
end
|
47
|
+
|
48
|
+
# package files and manifest in +zipfile+. If +force+ is true, we'll try to delete the target +zipfile+ if it already exists.
|
49
|
+
def pack(force=nil)
|
50
|
+
if File.exists? @zipfile
|
51
|
+
if force
|
52
|
+
File.delete @zipfile
|
53
|
+
else
|
54
|
+
raise "File #{@zipfile} already exists."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
dirname = File.dirname(@zipfile)
|
59
|
+
raise "Dir #{dirname} is not exists." unless File.exists? dirname
|
60
|
+
raise "Dir #{dirname} is not writable." unless File.writable? dirname
|
61
|
+
|
62
|
+
Zip::ZipFile.open(@zipfile, Zip::ZipFile::CREATE) do |zf|
|
63
|
+
# manifest file
|
64
|
+
zf.get_output_stream(MANIFEST_FILE) {|o| o << Yajl::Encoder.encode(@manifest)}
|
65
|
+
|
66
|
+
@files.each do |f, path|
|
67
|
+
zf.add("#{CONTENT_FOLDER}/#{f}", path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
begin
|
72
|
+
File.chmod(@filemode, @zipfile)
|
73
|
+
rescue => e
|
74
|
+
raise "Fail to change the mode of #{@zipfile} to #{@filemode.to_s(8)}: #{e}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# unpack the content to +path+ and return extraced file list.
|
79
|
+
def unpack path
|
80
|
+
raise "File #{@zipfile} not exists." unless File.exists? @zipfile
|
81
|
+
raise "unpack path: #{path} not found." unless Dir.exists? path
|
82
|
+
raise "unpack path: #{path} is not writable." unless File.writable? path
|
83
|
+
|
84
|
+
files = []
|
85
|
+
Zip::ZipFile.foreach(@zipfile) do |entry|
|
86
|
+
next if entry.to_s == MANIFEST_FILE
|
87
|
+
entry_name = File.basename entry.to_s
|
88
|
+
dst_path = File.join(path, entry_name)
|
89
|
+
dirname = File.dirname(dst_path)
|
90
|
+
FileUtils.mkdir_p(dirname) unless File.exists? dirname
|
91
|
+
files << dst_path
|
92
|
+
entry.extract(dst_path)
|
93
|
+
end
|
94
|
+
files.freeze
|
95
|
+
yield files if block_given?
|
96
|
+
files
|
97
|
+
rescue => e
|
98
|
+
# auto cleanup if error raised.
|
99
|
+
files.each{|f| File.delete f if File.exists? f} if files
|
100
|
+
raise ServiceError.new(ServiceError::FILE_CORRUPTED) if e.is_a? Zlib::DataError
|
101
|
+
raise e
|
102
|
+
end
|
103
|
+
|
104
|
+
# read manifest in a zip file
|
105
|
+
def load_manifest
|
106
|
+
zf = Zip::ZipFile.open(@zipfile)
|
107
|
+
@manifest = VCAP.symbolize_keys(Yajl::Parser.parse(zf.read(MANIFEST_FILE)))
|
108
|
+
rescue Errno::ENOENT => e
|
109
|
+
raise ServiceError.new(ServiceError::BAD_SERIALIZED_DATAFILE, "request. Missing manifest.")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|