uae-common 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/uae/common.rb +111 -0
  2. data/lib/uae/component.rb +171 -0
  3. metadata +47 -0
data/lib/uae/common.rb ADDED
@@ -0,0 +1,111 @@
1
+ require 'fileutils'
2
+ require 'socket'
3
+
4
+ module UAE
5
+
6
+ # 获取本机ip
7
+ def self.local_ip(route)
8
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
9
+ UDPSocket.open {|s| s.connect(route, 1); s.addr.last }
10
+ ensure
11
+ Socket.do_not_reverse_lookup = orig
12
+ end
13
+
14
+ # 获取一个uuid
15
+ def self.secure_uuid
16
+ result = File.open('/dev/urandom') { |x| x.read(16).unpack('H*')[0] }
17
+ end
18
+
19
+ # 获取一个随机端口
20
+ def self.grab_ephemeral_port
21
+ socket = TCPServer.new('0.0.0.0', 0)
22
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
23
+ Socket.do_not_reverse_lookup = true
24
+ port = socket.addr[1]
25
+ socket.close
26
+ return port
27
+ end
28
+
29
+ def self.process_running?(pid)
30
+ return false unless pid && (pid > 0)
31
+ output = %x[ps -o rss= -p #{pid}]
32
+ return true if ($? == 0 && !output.empty?)
33
+ # fail otherwise..
34
+ return false
35
+ end
36
+
37
+ class PidFile
38
+ class ProcessRunningError < StandardError
39
+ end
40
+
41
+ def initialize(pid_file, create_parents=true)
42
+ @pid_file = pid_file
43
+ @dirty = true
44
+ write(create_parents)
45
+ end
46
+
47
+ # Removes the created pidfile
48
+ def unlink()
49
+ return unless @dirty
50
+
51
+ # Swallowing exception here is fine. Removing the pid files is a courtesy.
52
+ begin
53
+ File.unlink(@pid_file)
54
+ @dirty = false
55
+ rescue
56
+ end
57
+ self
58
+ end
59
+
60
+ # Removes the created pidfile upon receipt of the supplied signals
61
+ def unlink_on_signals(*sigs)
62
+ return unless @dirty
63
+
64
+ sigs.each do |s|
65
+ Signal.trap(s) { unlink() }
66
+ end
67
+ self
68
+ end
69
+
70
+ def unlink_at_exit()
71
+ at_exit { unlink() }
72
+ self
73
+ end
74
+
75
+ def to_s()
76
+ @pid_file
77
+ end
78
+
79
+ protected
80
+
81
+ # Atomically writes the pidfile.
82
+ # NB: This throws exceptions if the pidfile contains the pid of another running process.
83
+ #
84
+ # +create_parents+ If true, all parts of the path up to the file's dirname will be created.
85
+ #
86
+ def write(create_parents=true)
87
+ FileUtils.mkdir_p(File.dirname(@pid_file)) if create_parents
88
+
89
+ # Protip from Wilson: binary mode keeps things sane under Windows
90
+ # Closing the fd releases our lock
91
+ File.open(@pid_file, 'a+b', 0644) do |f|
92
+ f.flock(File::LOCK_EX)
93
+
94
+ # Check if process is already running
95
+ pid = f.read().strip().to_i()
96
+ if pid == Process.pid()
97
+ return
98
+ elsif UAE.process_running?(pid)
99
+ raise ProcessRunningError.new("Process already running (pid=%d)." % (pid))
100
+ end
101
+
102
+ # We're good to go, write our pid
103
+ f.truncate(0)
104
+ f.rewind()
105
+ f.write("%d\n" % (Process.pid()))
106
+ f.flush()
107
+ end
108
+ end
109
+ end # class PidFile
110
+
111
+ end
@@ -0,0 +1,171 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ require "eventmachine"
3
+ require 'thin'
4
+ require "yajl"
5
+ require "nats/client"
6
+ require "base64"
7
+ require 'set'
8
+
9
+ module UAE
10
+
11
+ RACK_JSON_HDR = { 'Content-Type' => 'application/json' }
12
+ RACK_TEXT_HDR = { 'Content-Type' => 'text/plaintext' }
13
+
14
+ class Varz
15
+ def initialize(logger)
16
+ @logger = logger
17
+ end
18
+
19
+ def call(env)
20
+ @logger.debug "varz access"
21
+ varz = Yajl::Encoder.encode(Component.updated_varz, :pretty => true, :terminator => "\n")
22
+ [200, { 'Content-Type' => 'application/json', 'Content-Length' => varz.length.to_s }, varz]
23
+ rescue => e
24
+ @logger.error "varz error #{e.inspect} #{e.backtrace.join("\n")}"
25
+ raise e
26
+ end
27
+ end
28
+
29
+ class Healthz
30
+ def initialize(logger)
31
+ @logger = logger
32
+ end
33
+
34
+ def call(env)
35
+ @logger.debug "healthz access"
36
+ healthz = Component.updated_healthz
37
+ [200, { 'Content-Type' => 'application/json', 'Content-Length' => healthz.length.to_s }, healthz]
38
+ rescue => e
39
+ @logger.error "healthz error #{e.inspect} #{e.backtrace.join("\n")}"
40
+ raise e
41
+ end
42
+ end
43
+
44
+ # Common component setup for discovery and monitoring
45
+ class Component
46
+
47
+ # We will suppress these from normal varz reporting by default.
48
+ CONFIG_SUPPRESS = Set.new([:mbus, :service_mbus, :keys, :database_environment, :mysql, :password])
49
+
50
+ class << self
51
+
52
+ attr_reader :varz
53
+ attr_accessor :healthz
54
+
55
+ def updated_varz
56
+ @last_varz_update ||= 0
57
+ if Time.now.to_f - @last_varz_update >= 1
58
+ # Snapshot uptime
59
+ @varz[:uptime] = UAE.uptime_string(Time.now - @varz[:start])
60
+
61
+ # Grab current cpu and memory usage.
62
+ rss, pcpu = `ps -o rss=,pcpu= -p #{Process.pid}`.split
63
+ @varz[:mem] = rss.to_i
64
+ @varz[:cpu] = pcpu.to_f
65
+
66
+ @last_varz_update = Time.now.to_f
67
+ end
68
+ varz
69
+ end
70
+
71
+ def updated_healthz
72
+ @last_healthz_update ||= 0
73
+ if Time.now.to_f - @last_healthz_update >= 1
74
+ # ...
75
+ @last_healthz_update = Time.now.to_f
76
+ end
77
+
78
+ healthz
79
+ end
80
+
81
+ def start_http_server(host, port, auth, logger)
82
+ http_server = Thin::Server.new(host, port, :signals => false) do
83
+ Thin::Logging.silent = true
84
+ use Rack::Auth::Basic do |username, password|
85
+ [username, password] == auth
86
+ end
87
+ map '/healthz' do
88
+ run Healthz.new(logger)
89
+ end
90
+ map '/varz' do
91
+ run Varz.new(logger)
92
+ end
93
+ end
94
+ http_server.start!
95
+ end
96
+
97
+ def uuid
98
+ @discover[:uuid]
99
+ end
100
+
101
+ def register(opts)
102
+ uuid = UAE.secure_uuid
103
+ type = opts[:type]
104
+ index = opts[:index]
105
+ uuid = "#{index}-#{uuid}" if index
106
+ host = opts[:host] || UAE.local_ip
107
+ port = opts[:port] || UAE.grab_ephemeral_port
108
+ nats = opts[:nats] || NATS
109
+ auth = [opts[:user] || UAE.secure_uuid, opts[:password] || UAE.secure_uuid]
110
+ logger = opts[:logger] || Logger.new(nil)
111
+
112
+ # Discover message limited
113
+ @discover = {
114
+ :type => type,
115
+ :index => index,
116
+ :uuid => uuid,
117
+ :host => "#{host}:#{port}",
118
+ :credentials => auth,
119
+ :start => Time.now
120
+ }
121
+
122
+ # Varz is customizable
123
+ @varz = @discover.dup
124
+ @varz[:config] = sanitize_config(opts[:config]) if opts[:config]
125
+
126
+ @healthz = "ok\n".freeze
127
+
128
+ # Next steps require EM
129
+ raise "EventMachine reactor needs to be running" if !EventMachine.reactor_running?
130
+
131
+ # Startup the http endpoint for /varz and /healthz
132
+ start_http_server(host, port, auth, logger)
133
+
134
+ # Listen for discovery requests
135
+ nats.subscribe('vcap.component.discover') do |msg, reply|
136
+ update_discover_uptime
137
+ nats.publish(reply, @discover.to_json)
138
+ end
139
+
140
+ # Also announce ourselves on startup..
141
+ nats.publish('vcap.component.announce', @discover.to_json)
142
+ end
143
+
144
+ def update_discover_uptime
145
+ @discover[:uptime] = UAE.uptime_string(Time.now - @discover[:start])
146
+ end
147
+
148
+ def clear_level(h)
149
+ h.each do |k, v|
150
+ if CONFIG_SUPPRESS.include?(k.to_sym)
151
+ h.delete(k)
152
+ else
153
+ clear_level(h[k]) if v.instance_of? Hash
154
+ end
155
+ end
156
+ end
157
+
158
+ def sanitize_config(config)
159
+ # Can't Marshal/Deep Copy logger instances that services use
160
+ if config[:logger]
161
+ config = config.dup
162
+ config.delete(:logger)
163
+ end
164
+ # Deep copy
165
+ config = Marshal.load(Marshal.dump(config))
166
+ clear_level(config)
167
+ config
168
+ end
169
+ end
170
+ end
171
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uae-common
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - zhonggy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-15 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: UAE common library
15
+ email:
16
+ - zhonggy@ucweb.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/uae/common.rb
22
+ - lib/uae/component.rb
23
+ homepage: ''
24
+ licenses: []
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ! '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 1.8.23
44
+ signing_key:
45
+ specification_version: 3
46
+ summary: It works for UAE project
47
+ test_files: []