vcap_services_base 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
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