union_station_hooks_core 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,241 @@
1
+ # Union Station - https://www.unionstationapp.com/
2
+ # Copyright (c) 2010-2015 Phusion Holding B.V.
3
+ #
4
+ # "Union Station" and "Passenger" are trademarks of Phusion Holding B.V.
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+
24
+ require 'fileutils'
25
+ require 'net/http'
26
+ require 'uri'
27
+
28
+ module UnionStationHooks
29
+ # Contains helper methods for use in unit tests across all the
30
+ # `union_station_hooks_*` gems.
31
+ #
32
+ # @private
33
+ module SpecHelper
34
+ extend self # Make methods available as class methods.
35
+
36
+ def self.included(klass)
37
+ # When included into another class, make sure that Utils
38
+ # methods are made private.
39
+ public_instance_methods(false).each do |method_name|
40
+ klass.send(:private, method_name)
41
+ end
42
+ end
43
+
44
+ # To be called during initialization of the test suite.
45
+ def initialize!
46
+ load_passenger
47
+ initialize_ush_api
48
+ initialize_debugging
49
+ undo_bundler
50
+ end
51
+
52
+ # Lookup the `passenger-config` command, either by respecting the
53
+ # `PASSENGER_CONFIG` environment variable, or by looking it up in `PATH`.
54
+ # If the command cannot be found, the current process aborts with an
55
+ # error message.
56
+ def find_passenger_config
57
+ passenger_config = ENV['PASSENGER_CONFIG']
58
+ if passenger_config.nil? || passenger_config.empty?
59
+ ENV['PATH'].split(':').each do |path|
60
+ if File.exist?("#{path}/passenger-config")
61
+ passenger_config = "#{path}/passenger-config"
62
+ break
63
+ end
64
+ end
65
+ end
66
+ if passenger_config.nil? || passenger_config.empty?
67
+ abort 'ERROR: The unit tests are to be run against a specific ' \
68
+ 'Passenger version. However, the \'passenger-config\' command is ' \
69
+ 'not found. Please install Passenger, or (if you are sure ' \
70
+ 'Passenger is installed) set the PASSENGER_CONFIG environment ' \
71
+ 'variable to the \'passenger-config\' command.'
72
+ end
73
+ passenger_config
74
+ end
75
+
76
+ # Uses `find_passenger_config` to lookup a Passenger installation, and
77
+ # loads the Passenger Ruby support library associated with that
78
+ # installation. All the constants defined in the Passenger Ruby support
79
+ # library are loaded. In addition, checks whether the Passenger agent
80
+ # executable is installed. If not, the current process aborts with an
81
+ # error message.
82
+ def load_passenger
83
+ passenger_config = find_passenger_config
84
+ puts "Using Passenger installation at: #{passenger_config}"
85
+ passenger_ruby_libdir = `#{passenger_config} about ruby-libdir`.strip
86
+ require("#{passenger_ruby_libdir}/phusion_passenger")
87
+ PhusionPassenger.locate_directories
88
+ PhusionPassenger.require_passenger_lib 'constants'
89
+ puts "Loaded Passenger version #{PhusionPassenger::VERSION_STRING}"
90
+
91
+ agent = PhusionPassenger.find_support_binary(PhusionPassenger::AGENT_EXE)
92
+ if agent.nil?
93
+ abort "ERROR: The Passenger agent isn't installed. Please ensure " \
94
+ "that it is installed, e.g. using:\n\n" \
95
+ " #{passenger_config} install-agent\n\n"
96
+ end
97
+ end
98
+
99
+ def initialize_ush_api
100
+ UnionStationHooks.require_lib('api')
101
+ end
102
+
103
+ def initialize_debugging
104
+ @@debug = !ENV['DEBUG'].to_s.empty?
105
+ if @@debug
106
+ UnionStationHooks.require_lib('log')
107
+ UnionStationHooks::Log.debugging = true
108
+ end
109
+ end
110
+
111
+ # Unit tests must undo the Bundler environment so that the gem's
112
+ # own Gemfile doesn't affect subprocesses that may have their
113
+ # own Gemfile.
114
+ def undo_bundler
115
+ clean_env = nil
116
+ Bundler.with_clean_env do
117
+ clean_env = ENV.to_hash
118
+ end
119
+ ENV.replace(clean_env)
120
+ end
121
+
122
+ # Checks whether `initialize_debugging` enabled debugging mode.
123
+ def debug?
124
+ @@debug
125
+ end
126
+
127
+ # Writes the given content to the file at the given path. If or or more
128
+ # parent directories don't exist, then they are created.
129
+ def write_file(path, content)
130
+ dir = File.dirname(path)
131
+ if !File.exist?(dir)
132
+ FileUtils.mkdir_p(dir)
133
+ end
134
+ File.open(path, 'wb') do |f|
135
+ f.write(content)
136
+ end
137
+ end
138
+
139
+ def get_response(path)
140
+ uri = URI.parse("#{root_url}#{path}")
141
+ Net::HTTP.get_response(uri)
142
+ end
143
+
144
+ def get(path)
145
+ response = get_response(path)
146
+ return_200_response_body(path, response)
147
+ end
148
+
149
+ def return_200_response_body(path, response)
150
+ if response.code == '200'
151
+ response.body
152
+ else
153
+ raise "HTTP request to #{path} failed.\n" \
154
+ "Code: #{response.code}\n" \
155
+ "Body:\n" \
156
+ "#{response.body}"
157
+ end
158
+ end
159
+
160
+ # Opens a debug shell. By default, the debug shell is opened in the current
161
+ # working directory. If the current module has the `prepare_debug_shell`
162
+ # method, that method is called before opening the debug shell. The method
163
+ # could, for example, change the working directory.
164
+ #
165
+ # This method does *not* raise an exception if the debug shell exits with
166
+ # an error.
167
+ def debug_shell
168
+ puts '------ Opening debug shell -----'
169
+ @orig_dir = Dir.pwd
170
+ begin
171
+ if respond_to?(:prepare_debug_shell)
172
+ prepare_debug_shell
173
+ end
174
+ system('bash')
175
+ ensure
176
+ Dir.chdir(@orig_dir)
177
+ end
178
+ puts '------ Exiting debug shell -----'
179
+ end
180
+
181
+ # Returns the path of a specific UstRouter dump file.
182
+ # Requires that `@dump_dir` is set.
183
+ def dump_file_path(category = 'requests')
184
+ raise '@dump_dir variable required' if !@dump_dir
185
+ "#{@dump_dir}/#{category}"
186
+ end
187
+
188
+ # Reads the contents of a specific UstRouter dump file.
189
+ # Requires that `@dump_dir` is set.
190
+ #
191
+ # @raise SystemError Something went wrong during reading.
192
+ def read_dump_file(category = 'requests')
193
+ File.read(dump_file_path(category))
194
+ end
195
+
196
+ # Waits until the dump file exists. Raises an error if
197
+ # this doesn't become true within the default {#eventually}
198
+ # timeout.
199
+ def wait_for_dump_file_existance(category = 'requests')
200
+ eventually do
201
+ File.exist?(dump_file_path(category))
202
+ end
203
+ end
204
+
205
+ # Makes `UnionStationHooks::Log.warn` not print anything.
206
+ def silence_warnings
207
+ UnionStationHooks::Log.warn_callback = lambda { |_message| }
208
+ end
209
+
210
+ # Asserts that something should eventually happen. This is done by checking
211
+ # that the given block eventually returns true. The block is called
212
+ # once every `check_interval` msec. If the block does not return true
213
+ # within `deadline_duration` secs, then an exception is raised.
214
+ def eventually(deadline_duration = 3, check_interval = 0.05)
215
+ deadline = Time.now + deadline_duration
216
+ while Time.now < deadline
217
+ if yield
218
+ return
219
+ else
220
+ sleep(check_interval)
221
+ end
222
+ end
223
+ raise 'Time limit exceeded'
224
+ end
225
+
226
+ # Asserts that something should never happen. This is done by checking that
227
+ # the given block never returns true. The block is called once every
228
+ # `check_interval` msec, until `deadline_duration` seconds have passed.
229
+ # If the block ever returns true, then an exception is raised.
230
+ def should_never_happen(deadline_duration = 0.5, check_interval = 0.05)
231
+ deadline = Time.now + deadline_duration
232
+ while Time.now < deadline
233
+ if yield
234
+ raise "That which shouldn't happen happened anyway"
235
+ else
236
+ sleep(check_interval)
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,53 @@
1
+ # Union Station - https://www.unionstationapp.com/
2
+ # Copyright (c) 2010-2015 Phusion Holding B.V.
3
+ #
4
+ # "Union Station" and "Passenger" are trademarks of Phusion Holding B.V.
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+
24
+
25
+ module UnionStationHooks
26
+ # See {UnionStationHooks.now} for more information.
27
+ class TimePoint
28
+ # @api private
29
+ attr_reader :time, :utime, :stime
30
+
31
+ # @api private
32
+ def initialize(time, utime, stime)
33
+ @time = time
34
+ @utime = utime
35
+ @stime = stime
36
+ end
37
+
38
+ # @api private
39
+ def usec_timestamp
40
+ @time.to_i * 1_000_000 + @time.usec
41
+ end
42
+
43
+ # @api private
44
+ def usec
45
+ @time.usec
46
+ end
47
+
48
+ # @api private
49
+ def to_i
50
+ @time.to_i
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,182 @@
1
+ # Union Station - https://www.unionstationapp.com/
2
+ # Copyright (c) 2010-2015 Phusion Holding B.V.
3
+ #
4
+ # "Union Station" and "Passenger" are trademarks of Phusion Holding B.V.
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+
24
+ UnionStationHooks.require_lib 'log'
25
+ UnionStationHooks.require_lib 'context'
26
+ UnionStationHooks.require_lib 'time_point'
27
+ UnionStationHooks.require_lib 'utils'
28
+
29
+ module UnionStationHooks
30
+ # @private
31
+ class Transaction
32
+ attr_reader :txn_id
33
+
34
+ def initialize(connection, txn_id)
35
+ @connection = connection
36
+ @txn_id = txn_id
37
+ if connection
38
+ raise ArgumentError, 'Transaction ID required' if txn_id.nil?
39
+ connection.ref
40
+ end
41
+ end
42
+
43
+ def null?
44
+ !@connection || !@connection.connected?
45
+ end
46
+
47
+ def message(text)
48
+ if !@connection
49
+ log_message_to_null(text)
50
+ return
51
+ end
52
+
53
+ @connection.synchronize do
54
+ if !@connection.connected?
55
+ log_message_to_null(text)
56
+ return
57
+ end
58
+
59
+ UnionStationHooks::Log.debug('[Union Station log] ' \
60
+ "#{@txn_id} #{Utils.encoded_timestamp} #{text}")
61
+
62
+ io_operation do
63
+ @connection.channel.write('log', @txn_id, Utils.encoded_timestamp)
64
+ @connection.channel.write_scalar(text)
65
+ end
66
+ end
67
+ end
68
+
69
+ def log_activity_block(name, extra_info = nil)
70
+ has_error = false
71
+ log_activity_begin(name, UnionStationHooks.now, extra_info)
72
+ begin
73
+ yield
74
+ rescue Exception
75
+ has_error = true
76
+ is_closed = closed?
77
+ raise
78
+ ensure
79
+ if !is_closed
80
+ log_activity_end(name, UnionStationHooks.now, has_error)
81
+ end
82
+ end
83
+ end
84
+
85
+ def log_activity_begin(name, time = UnionStationHooks.now, extra_info = nil)
86
+ if extra_info
87
+ extra_info_base64 = Utils.base64(extra_info)
88
+ else
89
+ extra_info_base64 = nil
90
+ end
91
+ if time.is_a?(TimePoint)
92
+ message "BEGIN: #{name} (#{Utils.encoded_timestamp(time)}," \
93
+ "#{time.utime.to_s(36)},#{time.stime.to_s(36)}) " \
94
+ "#{extra_info_base64}"
95
+ else
96
+ message "BEGIN: #{name} (#{Utils.encoded_timestamp(time)})" \
97
+ " #{extra_info_base64}"
98
+ end
99
+ end
100
+
101
+ def log_activity_end(name, time = UnionStationHooks.now, has_error = false)
102
+ if time.is_a?(TimePoint)
103
+ if has_error
104
+ message "FAIL: #{name} (#{Utils.encoded_timestamp(time)}," \
105
+ "#{time.utime.to_s(36)},#{time.stime.to_s(36)})"
106
+ else
107
+ message "END: #{name} (#{Utils.encoded_timestamp(time)}," \
108
+ "#{time.utime.to_s(36)},#{time.stime.to_s(36)})"
109
+ end
110
+ else
111
+ if has_error
112
+ message "FAIL: #{name} (#{Utils.encoded_timestamp(time)})"
113
+ else
114
+ message "END: #{name} (#{Utils.encoded_timestamp(time)})"
115
+ end
116
+ end
117
+ end
118
+
119
+ def log_activity(name, begin_time, end_time, extra_info = nil,
120
+ has_error = false)
121
+ log_activity_begin(name, begin_time, extra_info)
122
+ log_activity_end(name, end_time, has_error)
123
+ end
124
+
125
+ def close(should_flush_to_disk = false)
126
+ return if !@connection
127
+
128
+ @connection.synchronize do
129
+ return if !@connection.connected?
130
+
131
+ begin
132
+ io_operation do
133
+ # We need an ACK here so that we the UstRouter doesn't end up
134
+ # processing the Core's openTransaction and closeTransaction pair
135
+ # before it has received this process's openTransaction command.
136
+ @connection.channel.write('closeTransaction', @txn_id,
137
+ Utils.encoded_timestamp, true)
138
+ Utils.process_ust_router_reply(@connection.channel,
139
+ "Error handling reply for 'closeTransaction' message")
140
+ if should_flush_to_disk
141
+ flush_to_disk
142
+ end
143
+ end
144
+ ensure
145
+ @connection.unref
146
+ @connection = nil
147
+ end
148
+ end
149
+ end
150
+
151
+ def closed?
152
+ return nil if !@connection
153
+ @connection.synchronize do
154
+ !@connection.connected?
155
+ end
156
+ end
157
+
158
+ private
159
+
160
+ def log_message_to_null(text)
161
+ UnionStationHooks::Log.debug('[Union Station log to null] ' \
162
+ "#{@txn_id} #{Utils.encoded_timestamp} #{text}")
163
+ end
164
+
165
+ def io_operation
166
+ yield
167
+ rescue SystemCallError, IOError => e
168
+ @connection.disconnect
169
+ UnionStationHooks::Log.warn(
170
+ "Error communicating with the UstRouter: #{e.message}")
171
+ rescue Exception => e
172
+ @connection.disconnect
173
+ raise e
174
+ end
175
+
176
+ def flush_to_disk
177
+ @connection.channel.write('flush')
178
+ Utils.process_ust_router_reply(@connection.channel,
179
+ "Error handling reply for 'flush' message")
180
+ end
181
+ end
182
+ end