vcap_services_base 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/lib/base/abstract.rb +11 -0
  3. data/lib/base/api/message.rb +31 -0
  4. data/lib/base/asynchronous_service_gateway.rb +529 -0
  5. data/lib/base/backup.rb +206 -0
  6. data/lib/base/barrier.rb +54 -0
  7. data/lib/base/base.rb +159 -0
  8. data/lib/base/base_async_gateway.rb +164 -0
  9. data/lib/base/base_job.rb +5 -0
  10. data/lib/base/catalog_manager_base.rb +67 -0
  11. data/lib/base/catalog_manager_v1.rb +225 -0
  12. data/lib/base/catalog_manager_v2.rb +291 -0
  13. data/lib/base/cloud_controller_services.rb +75 -0
  14. data/lib/base/datamapper_l.rb +148 -0
  15. data/lib/base/gateway.rb +167 -0
  16. data/lib/base/gateway_service_catalog.rb +68 -0
  17. data/lib/base/http_handler.rb +101 -0
  18. data/lib/base/job/async_job.rb +71 -0
  19. data/lib/base/job/config.rb +27 -0
  20. data/lib/base/job/lock.rb +153 -0
  21. data/lib/base/job/package.rb +112 -0
  22. data/lib/base/job/serialization.rb +365 -0
  23. data/lib/base/job/snapshot.rb +354 -0
  24. data/lib/base/node.rb +471 -0
  25. data/lib/base/node_bin.rb +154 -0
  26. data/lib/base/plan.rb +63 -0
  27. data/lib/base/provisioner.rb +1120 -0
  28. data/lib/base/provisioner_v1.rb +125 -0
  29. data/lib/base/provisioner_v2.rb +193 -0
  30. data/lib/base/service.rb +93 -0
  31. data/lib/base/service_advertiser.rb +184 -0
  32. data/lib/base/service_error.rb +122 -0
  33. data/lib/base/service_message.rb +94 -0
  34. data/lib/base/service_plan_change_set.rb +11 -0
  35. data/lib/base/simple_aop.rb +63 -0
  36. data/lib/base/snapshot_v2/snapshot.rb +227 -0
  37. data/lib/base/snapshot_v2/snapshot_client.rb +158 -0
  38. data/lib/base/snapshot_v2/snapshot_job.rb +95 -0
  39. data/lib/base/utils.rb +63 -0
  40. data/lib/base/version.rb +7 -0
  41. data/lib/base/warden/instance_utils.rb +161 -0
  42. data/lib/base/warden/node_utils.rb +205 -0
  43. data/lib/base/warden/service.rb +426 -0
  44. data/lib/base/worker_bin.rb +76 -0
  45. data/lib/vcap_services_base.rb +16 -0
  46. metadata +364 -0
@@ -0,0 +1,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