union_station_hooks_core 1.0.0

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.
@@ -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