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