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,206 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'optparse'
4
+ require 'timeout'
5
+ require 'fileutils'
6
+ require 'yaml'
7
+ require 'pathname'
8
+ require 'steno'
9
+
10
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', '..')
11
+ require 'vcap/common'
12
+
13
+ $:.unshift File.dirname(__FILE__)
14
+ require 'abstract'
15
+
16
+ module VCAP
17
+ module Services
18
+ module Base
19
+ end
20
+ end
21
+ end
22
+
23
+ #@config_file Full path to config file
24
+ #@config Config hash for config file
25
+ #@logger
26
+ #@nfs_base NFS base path
27
+ class VCAP::Services::Base::Backup
28
+ abstract :default_config_file
29
+ abstract :backup_db
30
+
31
+ def initialize
32
+ @run_lock = Mutex.new
33
+ @shutdown = false
34
+ trap("TERM") { exit_fun }
35
+ trap("INT") { exit_fun }
36
+ end
37
+
38
+ def script_file
39
+ $0
40
+ end
41
+
42
+ def exit_fun
43
+ @shutdown = true
44
+ Thread.new do
45
+ @run_lock.synchronize { exit }
46
+ end
47
+ end
48
+
49
+ def single_app(&blk)
50
+ if File.open(script_file).flock(File::LOCK_EX|File::LOCK_NB)
51
+ blk.call
52
+ else
53
+ echo "Script #{ script_file } is already running",true
54
+ end
55
+ end
56
+
57
+ def start
58
+ single_app do
59
+ echo "#{File.basename(script_file)} starts"
60
+ @config_file = default_config_file
61
+ parse_options
62
+
63
+ echo "Load config file"
64
+ # load conf file
65
+ begin
66
+ @config = YAML.load(File.open(@config_file))
67
+ rescue => e
68
+ echo "Could not read configuration file: #{e}",true
69
+ exit
70
+ end
71
+
72
+ # Setup logger
73
+ echo @config["logging"]
74
+ logging_config = Steno::Config.from_hash(@config["logging"])
75
+ Steno.init(logging_config)
76
+ # Use running binary name for logger identity name.
77
+ @logger = Steno.logger(File.basename(script_file))
78
+
79
+ # Make pidfile
80
+ if @config["pid"]
81
+ pf = VCAP::PidFile.new(@config["pid"])
82
+ pf.unlink_at_exit
83
+ end
84
+
85
+ echo "Check mount points"
86
+ check_mount_points
87
+
88
+ # make sure backup dir on nfs storage exists
89
+ @nfs_base = @config["backup_base_dir"] + "/backups/" + @config["service_name"]
90
+ echo "Check NFS base"
91
+ if File.directory? @nfs_base
92
+ echo @nfs_base + " exists"
93
+ else
94
+ echo @nfs_base + " does not exist, create it"
95
+ begin
96
+ FileUtils.mkdir_p @nfs_base
97
+ rescue => e
98
+ echo "Could not create dir on nfs!",true
99
+ exit
100
+ end
101
+ end
102
+ echo "Run backup task"
103
+ @run_lock.synchronize { backup_db }
104
+ echo "#{File.basename(script_file)} task is completed"
105
+ end
106
+ rescue => e
107
+ echo "Error: #{e.message}\n #{e.backtrace}",true
108
+ rescue Interrupt => it
109
+ echo "Backup is interrupted!"
110
+ end
111
+
112
+ def get_dump_path(name, options={})
113
+ name = name.sub(/^(mongodb|redis)-/,'')
114
+ mode = options[:mode] || 0
115
+ time = options[:time] || Time.now
116
+ case mode
117
+ when 1
118
+ File.join(@config['backup_base_dir'], 'backups', @config['service_name'],name, time.to_i.to_s,@config['node_id'])
119
+ else
120
+ File.join(@config['backup_base_dir'], 'backups', @config['service_name'], name[0,2], name[2,2], name[4,2], name, time.to_i.to_s)
121
+ end
122
+ end
123
+
124
+ def check_mount_points
125
+ # make sure the backup base dir is mounted
126
+ pn = Pathname.new(@config["backup_base_dir"])
127
+ if !@tolerant && !pn.mountpoint?
128
+ echo @config["backup_base_dir"] + " is not mounted, exit",true
129
+ exit
130
+ end
131
+ end
132
+
133
+ def echo(output, err=false)
134
+ if err
135
+ $stderr.puts(output) unless @logger
136
+ @logger.error(output) if @logger
137
+ else
138
+ $stdout.puts(output) unless @logger
139
+ @logger.info(output) if @logger
140
+ end
141
+ end
142
+
143
+ def parse_options
144
+ OptionParser.new do |opts|
145
+ opts.banner = "Usage: #{File.basename(script_file)} [options]"
146
+ opts.on("-c", "--config [ARG]", "Node configuration File") do |opt|
147
+ @config_file = opt
148
+ end
149
+ opts.on("-h", "--help", "Help") do
150
+ puts opts
151
+ exit
152
+ end
153
+ opts.on("-t", "--tolerant", "Tolerant mode") do
154
+ @tolerant = true
155
+ end
156
+ more_options(opts)
157
+ end.parse!
158
+ end
159
+
160
+ def more_options(opts)
161
+ end
162
+ end
163
+
164
+ class CMDHandle
165
+
166
+ def initialize(cmd, timeout=nil, &blk)
167
+ @cmd = cmd
168
+ @timeout = timeout
169
+ @errback = blk
170
+ end
171
+
172
+ def run
173
+ pid = fork
174
+ if pid
175
+ # parent process
176
+ success = false
177
+ begin
178
+ success = Timeout::timeout(@timeout) do
179
+ Process.waitpid(pid)
180
+ value = $?.exitstatus
181
+ @errback.call(@cmd, value, "No message.") if value != 0 && @errback
182
+ return value == 0
183
+ end
184
+ rescue Timeout::Error
185
+ Process.detach(pid)
186
+ Process.kill("KILL", pid)
187
+ @errback.call(@cmd, -1, "Killed due to timeout.") if @errback
188
+ return false
189
+ end
190
+ else
191
+ begin
192
+ # child process
193
+ exec(@cmd)
194
+ rescue => e
195
+ exit!
196
+ end
197
+ end
198
+ end
199
+
200
+ def self.execute(cmd, timeout = nil, *args)
201
+ errb = args.pop if args.last.is_a? Proc
202
+ instance = self.new(cmd, timeout, &errb)
203
+ instance.run
204
+ end
205
+ end
206
+
@@ -0,0 +1,54 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require "eventmachine"
3
+ require "thread"
4
+
5
+ module VCAP
6
+ module Services
7
+ module Base
8
+ end
9
+ end
10
+ end
11
+
12
+ class VCAP::Services::Base::Barrier
13
+
14
+ def initialize(options = {}, &callback)
15
+ raise ArgumentError unless options[:timeout] || options[:callbacks]
16
+ @lock = Mutex.new
17
+ @callback = callback
18
+ @expected_callbacks = options[:callbacks]
19
+ @timer = EM.add_timer(options[:timeout]) {on_timeout} if options[:timeout]
20
+ @callback_fired = false
21
+ @responses = []
22
+ @barrier_callback = Proc.new {|*args| call(*args)}
23
+ end
24
+
25
+ def on_timeout
26
+ @lock.synchronize do
27
+ unless @callback_fired
28
+ @callback_fired = true
29
+ @callback.call(@responses)
30
+ end
31
+ end
32
+ end
33
+
34
+ def call(*args)
35
+ @lock.synchronize do
36
+ unless @callback_fired
37
+ @responses << args
38
+ if @expected_callbacks
39
+ @expected_callbacks -= 1
40
+ if @expected_callbacks <= 0
41
+ EM.cancel_timer(@timer) if @timer
42
+ @callback_fired = true
43
+ @callback.call(@responses)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def callback
51
+ @barrier_callback
52
+ end
53
+
54
+ end
@@ -0,0 +1,159 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require 'eventmachine'
3
+ require 'vcap/common'
4
+ require 'vcap/component'
5
+ require 'nats/client'
6
+
7
+ $LOAD_PATH.unshift File.dirname(__FILE__)
8
+ require 'abstract'
9
+ require 'service_error'
10
+
11
+ module VCAP
12
+ module Services
13
+ module Base
14
+ class Base
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ class Object
21
+ def deep_dup
22
+ Marshal::load(Marshal.dump(self))
23
+ end
24
+ end
25
+
26
+ class VCAP::Services::Base::Base
27
+
28
+ include VCAP::Services::Base::Error
29
+
30
+ attr_reader :closing
31
+
32
+ def initialize(options)
33
+ @logger = options[:logger]
34
+ @options = options
35
+ @local_ip = VCAP.local_ip(options[:ip_route])
36
+ @logger.info("#{service_description}: Initializing")
37
+ @closing = false
38
+
39
+ @node_nats = nil
40
+ if options[:mbus]
41
+ NATS.on_error do |e|
42
+ # p $!
43
+ # puts $!.backtrace.join("\n")
44
+ @logger.error("Exiting due to NATS error: #{e}")
45
+ shutdown
46
+ exit
47
+ end
48
+ @logger.debug("Connecting with NATS")
49
+ @node_nats = NATS.connect(:uri => options[:mbus]) do
50
+ status_port = status_user = status_password = nil
51
+ if not options[:status].nil?
52
+ status_port = options[:status][:port]
53
+ status_user = options[:status][:user]
54
+ status_password = options[:status][:password]
55
+ end
56
+
57
+ @logger.debug("Registering with NATS")
58
+ VCAP::Component.register(
59
+ :nats => @node_nats,
60
+ :type => service_description,
61
+ :host => @local_ip,
62
+ :index => options[:index] || 0,
63
+ :config => options,
64
+ :port => status_port,
65
+ :user => status_user,
66
+ :password => status_password
67
+ )
68
+ on_connect_node
69
+ end
70
+ else
71
+ @logger.info("NATS is disabled")
72
+ end
73
+
74
+ @max_nats_payload = options[:max_nats_payload] || 1024 * 1024
75
+ end
76
+
77
+ def service_description()
78
+ return "#{service_name}-#{flavor}"
79
+ end
80
+
81
+ def publish(reply, msg)
82
+ # nats publish are only allowed to be called in reactor thread.
83
+ EM.schedule do
84
+ @node_nats.publish(reply, msg) if @node_nats
85
+ end
86
+ end
87
+
88
+ def update_varz()
89
+ vz = varz_details
90
+ vz.each { |k,v|
91
+ VCAP::Component.varz[k] = v
92
+ } if vz
93
+ end
94
+
95
+ def shutdown()
96
+ @closing = true
97
+ @logger.info("#{service_description}: Shutting down")
98
+ @node_nats.close if @node_nats
99
+ end
100
+
101
+ def group_handles_in_json(instances_list, bindings_list, size_limit)
102
+ while instances_list.count > 0 or bindings_list.count > 0
103
+ ins_list = []
104
+ bind_list = []
105
+ send_len = 0
106
+ idx_ins = 0
107
+ idx_bind = 0
108
+
109
+ instances_list.each do |ins|
110
+ len = ins.to_json.size + 1
111
+ if send_len + len < size_limit
112
+ send_len += len
113
+ idx_ins += 1
114
+ ins_list << ins
115
+ else
116
+ break
117
+ end
118
+ end
119
+ instances_list.slice!(0, idx_ins) if idx_ins > 0
120
+
121
+ bindings_list.each do |bind|
122
+ len = bind.to_json.size + 1
123
+ if send_len + len < size_limit
124
+ send_len += len
125
+ idx_bind += 1
126
+ bind_list << bind
127
+ else
128
+ break
129
+ end
130
+ end
131
+ bindings_list.slice!(0, idx_bind) if idx_bind > 0
132
+
133
+ # Generally, the size_limit is far more bigger than the length
134
+ # of any handles. If there's a huge handle or the size_limit is too
135
+ # small that the size_limit can't contain one handle the in one batch,
136
+ # we have to break the loop if no handle can be stuffed into batch.
137
+ if ins_list.count == 0 and bind_list.count == 0
138
+ @logger.warn("NATS message limit #{size_limit} is too small.")
139
+ break
140
+ else
141
+ yield ins_list, bind_list
142
+ end
143
+ end
144
+ end
145
+
146
+ # Subclasses VCAP::Services::Base::{Node,Provisioner} implement the
147
+ # following methods. (Note that actual service Provisioner or Node
148
+ # implementations should NOT need to touch these!)
149
+
150
+ # TODO on_connect_node should be on_connect_nats
151
+ abstract :on_connect_node
152
+ abstract :flavor # "Provisioner" or "Node"
153
+ abstract :varz_details
154
+
155
+ # Service Provisioner and Node classes must implement the following
156
+ # method
157
+ abstract :service_name
158
+
159
+ end
@@ -0,0 +1,164 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ # XXX(mjp)
3
+ require 'rubygems'
4
+
5
+ require 'eventmachine'
6
+ require 'em-http-request'
7
+ require 'json'
8
+ require 'sinatra/base'
9
+ require 'uri'
10
+ require 'thread'
11
+ require 'json_message'
12
+ require 'services/api'
13
+ require 'services/api/const'
14
+
15
+ $:.unshift(File.dirname(__FILE__))
16
+ require 'service_error'
17
+
18
+ module VCAP
19
+ module Services
20
+ end
21
+ end
22
+
23
+ # A simple service gateway that proxies requests onto an asynchronous service provisioners.
24
+ # NB: Do not use this with synchronous provisioners, it will produce unexpected results.
25
+ #
26
+ # TODO(mjp): This needs to handle unknown routes
27
+ class VCAP::Services::BaseAsynchronousServiceGateway < Sinatra::Base
28
+
29
+ include VCAP::Services::Base::Error
30
+
31
+ # Allow our exception handlers to take over
32
+ set :raise_errors, Proc.new {false}
33
+ set :show_exceptions, false
34
+
35
+ def initialize(opts)
36
+ super
37
+
38
+ @logger = opts[:logger]
39
+
40
+ setup(opts)
41
+ end
42
+
43
+ # Validate the incoming request
44
+ before do
45
+ Steno.config.context.data["request_guid"] = env['HTTP_X_VCAP_REQUEST_ID']
46
+
47
+ validate_incoming_request
48
+
49
+ content_type :json
50
+ end
51
+
52
+ after do
53
+ Steno.config.context.data.delete("request_guid")
54
+ end
55
+
56
+ # Handle errors that result from malformed requests
57
+ error [JsonMessage::ValidationError, JsonMessage::ParseError] do
58
+ error_msg = ServiceError.new(ServiceError::MALFORMATTED_REQ).to_hash
59
+ abort_request(error_msg)
60
+ end
61
+
62
+ # setup the environment
63
+ def setup(opts)
64
+ end
65
+
66
+ # Custom request validation
67
+ def validate_incoming_request
68
+ end
69
+
70
+ #################### Helpers ####################
71
+
72
+ helpers do
73
+
74
+ # Aborts the request with the supplied errs
75
+ #
76
+ # +errs+ Hash of section => err
77
+ def abort_request(error_msg)
78
+ err_body = error_msg['msg'].to_json()
79
+ halt(error_msg['status'], {'Content-Type' => Rack::Mime.mime_type('.json')}, err_body)
80
+ end
81
+
82
+ def auth_token
83
+ @auth_token ||= request_header(VCAP::Services::Api::GATEWAY_TOKEN_HEADER)
84
+ @auth_token
85
+ end
86
+
87
+ def request_body
88
+ request.body.rewind
89
+ request.body.read
90
+ end
91
+
92
+ def request_header(header)
93
+ # This is pretty ghetto but Rack munges headers, so we need to munge them as well
94
+ rack_hdr = "HTTP_" + header.upcase.gsub(/-/, '_')
95
+ env[rack_hdr]
96
+ end
97
+
98
+ def async_mode(timeout=@node_timeout)
99
+ request.env['__async_timer'] = EM.add_timer(timeout) do
100
+ @logger.warn("Request timeout in #{timeout} seconds.")
101
+ error_msg = ServiceError.new(ServiceError::SERVICE_UNAVAILABLE).to_hash
102
+ err_body = error_msg['msg'].to_json()
103
+ request.env['async.callback'].call(
104
+ [
105
+ error_msg['status'],
106
+ {'Content-Type' => Rack::Mime.mime_type('.json')},
107
+ err_body
108
+ ]
109
+ )
110
+ end unless request.env['done'] ||= false
111
+ throw :async
112
+ end
113
+
114
+ def async_reply(resp='{}')
115
+ async_reply_raw(200, {'Content-Type' => Rack::Mime.mime_type('.json')}, resp)
116
+ end
117
+
118
+ def async_reply_raw(status, headers, body)
119
+ @logger.debug("Reply status:#{status}, headers:#{headers}, body:#{body}")
120
+ @provisioner.update_responses_metrics(status) if @provisioner
121
+ request.env['done'] = true
122
+ EM.cancel_timer(request.env['__async_timer']) if request.env['__async_timer']
123
+ request.env['async.callback'].call([status, headers, body])
124
+ end
125
+
126
+ def async_reply_error(error_msg)
127
+ err_body = error_msg['msg'].to_json()
128
+ async_reply_raw(error_msg['status'], {'Content-Type' => Rack::Mime.mime_type('.json')}, err_body)
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def add_proxy_opts(req)
135
+ req[:proxy] = @proxy_opts
136
+ # this is a workaround for em-http-requesr 0.3.0 so that headers are not lost
137
+ # more info: https://github.com/igrigorik/em-http-request/issues/130
138
+ req[:proxy][:head] = req[:head]
139
+ end
140
+
141
+ def create_http_request(args)
142
+ req = {
143
+ :head => args[:head],
144
+ :body => args[:body],
145
+ }
146
+
147
+ if (@proxy_opts)
148
+ add_proxy_opts(req)
149
+ end
150
+
151
+ req
152
+ end
153
+
154
+ def make_logger(level=Logger::INFO)
155
+ logger = Logger.new(STDOUT)
156
+ logger.level = level
157
+ logger
158
+ end
159
+
160
+ def http_uri(uri)
161
+ uri = "http://#{uri}" unless (uri.index('http://') == 0 || uri.index('https://') == 0)
162
+ uri
163
+ end
164
+ end