net-tnsping 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.
Files changed (6) hide show
  1. data/CHANGES +39 -0
  2. data/MANIFEST +11 -0
  3. data/README +189 -0
  4. data/lib/net/tnsping.rb +205 -0
  5. data/test/tc_tns_ping.rb +112 -0
  6. metadata +48 -0
data/CHANGES ADDED
@@ -0,0 +1,39 @@
1
+ == 1.0.0 - 7-Jun-2005
2
+ * Moved project to RubyForge.
3
+ * Added a .gemspec file.
4
+ * Bumped the VERSION number.
5
+ * Inlined some rdoc code into tnsping.rb.
6
+
7
+ == 0.2.1 - 18-Apr-2005
8
+ * Fixed a bug in the tnsnames.ora parser method for those cases where each
9
+ db entry is on its own line.
10
+ * Corrected the date of the last release, and added the 0.1.1 and 0.1.2
11
+ changes back into the CHANGES file.
12
+
13
+ == 0.2.0 - 12-Apr-2005
14
+ * Massive internal refactoring.
15
+ * No longer requires that each tnsnames.ora entry be on its own line.
16
+ * Much improved documentation.
17
+ * Moved the sample program to the 'examples' directory.
18
+ * Many more tests added.
19
+ * Removed the INSTALL file. Installation instructions are now included in
20
+ the README file.
21
+ * The README and CHANGES files are now rdoc friendly.
22
+
23
+ == 0.1.2 - 11-Apr-2003
24
+ * Fixed a regex bug in the parse_tns_file() method that could cause problems
25
+ retrieving the correct host and/or port.
26
+ * Fixed the testsuite up a bit.
27
+
28
+ == 0.1.1 - 20-Mar-2003
29
+ * The default domain/zone from the sqlnet.ora file are automatically appended
30
+ to the check against the tnsnames.ora file (if present).
31
+ * Slightly better internal error handling added
32
+ * Added VERSION constant and class method
33
+ * Some more assertions added, and some minor changes, to the test suite
34
+ * rd2 documentation separated from source - now in /doc directory
35
+ * Changed CHANGELOG to CHANGES (merely for consistency with my other packages).
36
+ * Doc updates
37
+
38
+ == 0.1.0 - 19-Dec-2002
39
+ * Initial Release
data/MANIFEST ADDED
@@ -0,0 +1,11 @@
1
+ CHANGES
2
+ MANIFEST
3
+ README
4
+ install.rb
5
+ net-tnsping.gemspec
6
+
7
+ examples/test_tnsping.rb
8
+
9
+ lib/net/tnsping.rb
10
+
11
+ test/tc_tns_ping.rb
data/README ADDED
@@ -0,0 +1,189 @@
1
+ == Description
2
+ This package emulates the Oracle tnsping utility.
3
+
4
+ == Prerequisites
5
+ Ruby 1.8.0 or later
6
+ dbi
7
+ net-pingsimple 0.3.1 or later
8
+ Any of the Oracle database drivers (oracle, oci8, ruby9i, etc)
9
+
10
+ == Installation
11
+
12
+ === Manual Installation
13
+ ruby test/tc_tns_ping.rb (optional)
14
+ ruby install.rb
15
+
16
+ === Gem Installation
17
+ ruby net-tnsping.gemspec
18
+ gem install net-tnsping-<version>.gem
19
+
20
+ === Installation Note
21
+ You will have to manually tweak one instance variable (@db) in the
22
+ test suite in order to get all tests to pass. Set it to a known database
23
+ name.
24
+
25
+ == Synopsis
26
+ require "net/tnsping"
27
+ include Net
28
+
29
+ t = TNSPing.new("my_db")
30
+
31
+ if t.ping?
32
+ puts "Database appears to be up and running"
33
+ else
34
+ puts "There was a problem: " + t.exception
35
+ end
36
+
37
+ == Constants
38
+ VERSION
39
+ The version number of this package, returned as a String.
40
+
41
+ == Class Methods
42
+ TNSPing.new(db, driver="OCI8", host=nil, port=1521, timeout=5)
43
+ Creates and returns a new TNSPing object. If the db specified cannot be
44
+ found in the tnsnames.ora file, then a TNSPingError is raised.
45
+
46
+ == Instance Methods
47
+ TNSPing#db
48
+ Returns the name of the database that the TNSPing#ping_database? method
49
+ will attempt to connect to.
50
+
51
+ TNSPing#db=(database)
52
+ Sets the name of the database that the TNSPing#ping_database? method
53
+ will attempt to connect to.
54
+
55
+ TNSPing#driver
56
+ Returns the name of the driver used by the TNSPing#ping_database? method.
57
+ The default is OCI8.
58
+
59
+ TNSPing#driver=(driver)
60
+ Sets the name of the driver used by the TNSPing#ping_database? method.
61
+
62
+ TNSPing#dsn
63
+ Returns the dsn string that is used by TNSPing#ping_database?.
64
+
65
+ TNSPing#dsn=(string)
66
+ Sets the dsn string that is used by TNSPing#ping_database?. It should
67
+ be in the form "dbi:driver:database".
68
+
69
+ TNSPing#host
70
+ Returns the host that the TNSPing#ping_listener? call will use. This may
71
+ be nil, in which case, the TNSPing#hosts method will list which hosts it
72
+ will attempt to ping.
73
+
74
+ TNSPing#host=(host)
75
+ Sets the host that the TNSPing#ping_listener? call will use. If this is
76
+ set, then a ping will only be attempted against this host, irregardless
77
+ of what is in the tnsnames.ora file.
78
+
79
+ TNSPing#oracle_home
80
+ Returns the path to the oracle home directory. The default is either
81
+ ENV["ORACLE_HOME"] or ENV["ORA_HOME"].
82
+
83
+ TNSPing#oracle_home=(path)
84
+ Sets the path to the oracle home directory.
85
+
86
+ TNSPing#ping?
87
+ TNSPing#ping_listener?
88
+ Performs a TCP ping on the listener. The host and port are determined from
89
+ your tnsnames.ora file. If more than one host and/or port are found in the
90
+ tnsnames.ora file, then each will be tried. So long as at least one of
91
+ them connects successfully, true is returned.
92
+
93
+ If you specify a host and port in the constructor, then the attempt will
94
+ only be made against that host on the given port.
95
+
96
+ TNSPing#ping_database?(dsn=nil, timeout=nil, user=nil, passwd=nil)
97
+ Attempts to make a connection using a bogus login and password via the DBI
98
+ class. If an ORA-01017 Oracle error is returned, that means the database
99
+ is up and running and true is returned.
100
+
101
+ Note that each of the arguments for this method use the defaults passed
102
+ to the constructor (or have a default otherwise set). You generally
103
+ should not pass any arguments to this method.
104
+
105
+ In the event that this method fails, false is returned and the error can
106
+ be viewed via TNSPing#exception.
107
+
108
+ TNSPing#ping_all?
109
+ A shortcut method that merely calls TNSPing#ping? and TNSPing#ping_database?
110
+ in succession. Returns true if both succeed, false otherwise.
111
+
112
+ TNSPing#port
113
+ Returns the port that the TNSPing#ping_listener? will ping against. This
114
+ may be nil, in which case the TNSPing#ports method will list which ports
115
+ it will attempt to ping against.
116
+
117
+ TNSPing#port=(num)
118
+ Sets the port that the TNSPing#ping_listener? method will use. If this is
119
+ set, then a ping will only be attempted on this port, irregardless of what
120
+ is in the tnsnames.ora file.
121
+
122
+ TNSPing#timeout
123
+ Returns the timeout value used by all internal ping methods.
124
+
125
+ TNSPing#timeout=(seconds)
126
+ Sets the timeout used by all internal ping methods.
127
+
128
+ TNSPing#tns_admin
129
+ Returns the path to the tns admin directory. The default is
130
+ ENV["TNS_ADMIN"].
131
+
132
+ TNSPing#tns_admin=(path)
133
+ Sets the path to the tns admin directory.
134
+
135
+ TNSPing#tns_file
136
+ Returns the full path to the tnsnames.ora file.
137
+
138
+ TNSPing#tns_file=(path)
139
+ Sets the path to the tnsnames.ora file.
140
+
141
+ == Notes
142
+ The TNSPing class is a subclass of PingTCP.
143
+
144
+ This is NOT a wrapper for the tnsping utility. It performs two actions.
145
+ First, it attempts to ping the database using the appropriate port via TCP.
146
+ Then, it attempts to make a connection using a bogus name and password,
147
+ looking for error ORA-1017.
148
+
149
+ == Potential Questions
150
+ Q: "Why didn't you just wrap a backticked tnsping call?"
151
+
152
+ A: First, because the output is unpredictable. On two different platforms I
153
+ received two different output streams for the same datasource. Who knows
154
+ what it's like on other platforms?
155
+
156
+ Second, tnsping *only* tells you if the listener is up. It doesn't
157
+ actually test that the datasource is up and running.
158
+
159
+ Q: "Why didn't you write an extension using the tnsping source?"
160
+
161
+ A: Because the source is closed. One Oracle DBA told me that even attempting
162
+ to reverse-engineer the source was prohibited and that I should quit wasting
163
+ my time. Well, I wasted my time anyway, and came up with an even better and
164
+ more reliable solution.
165
+
166
+ == Known Bugs
167
+ None that I'm aware of. Please log any bug reports on the project page
168
+ at http://www.rubyforge.org/projects/shards
169
+
170
+ == Acknowledgements
171
+ Thanks go to Bryan Stanaway, a colleague and Oracle DBA who helped me out
172
+ with some questions I had.
173
+
174
+ == Copyright
175
+ (C) 2003-2005 Daniel J. Berger
176
+ All rights reserved.
177
+
178
+ == Warranty
179
+ This package is provided "as is" and without any express or
180
+ implied warranties, including, without limitation, the implied
181
+ warranties of merchantability and fitness for a particular purpose.
182
+
183
+ == License
184
+ Ruby's
185
+
186
+ == Author
187
+ Daniel J. Berger
188
+ djberg96 at yahoo dot com
189
+ imperator on IRC (irc.freenode.net)
@@ -0,0 +1,205 @@
1
+ require "dbi"
2
+ require "net/pingsimple"
3
+
4
+ module Net
5
+ class TNSPingError < StandardError; end
6
+ class TNSPing < PingTCP
7
+
8
+ VERSION = "1.0.0"
9
+
10
+ attr_accessor :db, :dsn, :tns_file, :host, :tns_admin
11
+ attr_accessor :oracle_home, :driver, :timeout
12
+ attr_reader :hosts, :ports, :port
13
+
14
+ # Creates and returns a new TNSPing object. If the db specified cannot
15
+ # be found in the tnsnames.ora file, then a TNSPingError is raised.
16
+ #
17
+ def initialize(db, driver="OCI8", host=nil, port=1521, timeout=5)
18
+ @db = db
19
+ @dsn = "dbi:#{driver}:" << db
20
+ @timeout = timeout
21
+ @port = port
22
+ @tns_admin = tns_admin
23
+ @ports = [] # There can be more than one host/port
24
+ @hosts = [] # for each dsn. Try them in order.
25
+ @sid = nil
26
+
27
+ @tns_admin = ENV["TNS_ADMIN"]
28
+ @oracle_home = ENV["ORACLE_HOME"] || ENV["ORA_HOME"]
29
+
30
+ if @tns_admin
31
+ @tns_file = @tns_admin + "/tnsnames.ora"
32
+ elsif @oracle_home
33
+ @tns_file = @oracle_home + "/network/admin/tnsnames.ora"
34
+ else
35
+ @tns_file = ENV["HOME"] + "/tnsnames.ora"
36
+ end
37
+
38
+ yield self if block_given?
39
+
40
+ # If the host is not specified, look for it in the tnsnames.or file
41
+ if host.nil?
42
+ err_msg = "tnsnames.ora file could not be found"
43
+ raise TNSPingError, err_msg unless File.exists?(@tns_file)
44
+ parse_tns_file
45
+ else
46
+ @hosts.push(host)
47
+ @ports.push(port)
48
+ end
49
+ end
50
+
51
+ # Sets the port that the TNSPing#ping_listener? method will use. If
52
+ # this is set, then a ping will only be attempted on this port,
53
+ # regardless of what is in the tnsnames.ora file.
54
+ #
55
+ def port=(num)
56
+ @ports = [num]
57
+ end
58
+
59
+ # Performs a TCP ping on the listener. The host and port are determined from
60
+ # your tnsnames.ora file. If more than one host and/or port are found in the
61
+ # tnsnames.ora file, then each will be tried. So long as at least one of
62
+ # them connects successfully, true is returned.
63
+ #
64
+ # If you specify a host and port in the constructor, then the attempt will
65
+ # only be made against that host on the given port.
66
+ #--
67
+ # Try each host/port listed for a given entry. Return a true result if
68
+ # any one of them succeeds and break out of the loop.
69
+ #
70
+ def ping?
71
+ if @hosts.empty?
72
+ raise TNSPingError, "No hosts found"
73
+ end
74
+
75
+ # Use 1521 if no ports were found in the tnsnames.ora file.
76
+ if @ports.empty?
77
+ @ports.push(@port)
78
+ end
79
+
80
+ # If the host is provided, only ping that host
81
+ if @host
82
+ 0.upto(@ports.length-1){ |n|
83
+ @port = @ports[n]
84
+ return super
85
+ }
86
+ else
87
+ 0.upto(@ports.length-1){ |n|
88
+ @port = @ports[n]
89
+ @host = @hosts[n]
90
+ return super
91
+ }
92
+ end
93
+ end
94
+
95
+ # Attempts to make a connection using a bogus login and password via the
96
+ # DBI class. If an ORA-01017 Oracle error is returned, that means the
97
+ # database is up and running and true is returned.
98
+ #
99
+ # Note that each of the arguments for this method use the defaults
100
+ # passed to the constructor (or have a default otherwise set). You
101
+ # generally should not pass any arguments to this method.
102
+ # In the event that this method fails, false is returned and the error
103
+ # can be viewed via TNSPing#exception.
104
+ #--
105
+ # I have intentionally set the user and password to something zany in
106
+ # order to avoid the possibility of accidentally guessing them. In
107
+ # case of cosmic coincidence, set them yourself.
108
+ #
109
+ def ping_database?(dsn=@dsn, timeout=@timeout, user=@sid, passwd="xz949")
110
+ re = /ORA-01017/
111
+ dbh = nil
112
+ user ||= "pzoyadf244"
113
+ rv = false
114
+ begin
115
+ Timeout.timeout(timeout){
116
+ dbh = DBI.connect(dsn,user,passwd)
117
+ }
118
+ rescue DBI::DatabaseError => e
119
+ if re.match(e.to_s)
120
+ rv = true
121
+ else
122
+ @exception = e
123
+ end
124
+ rescue Timeout::Error, StandardError => e
125
+ @exception = e
126
+ ensure
127
+ if dbh
128
+ dbh.disconnect if dbh.connected?
129
+ end
130
+ end
131
+ rv
132
+ end
133
+
134
+ # Simple wrapper for ping_listener? + ping_database?
135
+ #
136
+ def ping_all?
137
+ return false unless self.ping_listener?
138
+ return false unless self.ping_database?
139
+ true
140
+ end
141
+
142
+ private
143
+
144
+ # parse_tns_file
145
+ #
146
+ # Search for the dsn entry within the tnsnames.ora file and get the host
147
+ # and port information. Private method.
148
+ #
149
+ def parse_tns_file(file=@tns_file, db=@db)
150
+ re_blank = /^$/
151
+ re_comment = /^#/
152
+ re_tns_sentry = /^#{db}.*?=/ # specific entry
153
+ re_tns_gentry = /^\w.*?=/ # generic entry
154
+ re_tns_pair = /\w+\s*\=\s*[\w\.]+/ # used to parse key=val
155
+ re_keys = /\bhost\b|\bport\b|\bsid\b/i
156
+
157
+ data_string = ""
158
+ found = false
159
+
160
+ IO.foreach(file){ |line|
161
+ next if re_blank.match(line)
162
+ next if re_comment.match(line)
163
+ line.chomp!
164
+
165
+ # Skip over lines until an entry for the db is found.
166
+ match = re_tns_sentry.match(line)
167
+ if match
168
+ found = true
169
+ data_string << match.post_match # slurp the rest of the line
170
+ next
171
+ end
172
+
173
+ # Once found, slurp the lines into a variable until the next
174
+ # db entry is encountered.
175
+ if found
176
+ break if re_tns_gentry.match(line)
177
+ line.strip!
178
+ data_string << line
179
+ end
180
+ }
181
+
182
+ unless found
183
+ raise TNSPingError, "unable to find #{db} in #{file}"
184
+ end
185
+
186
+ # Break each 'key = value' line into its parts
187
+ data_string.scan(re_tns_pair).each{ |pair|
188
+ key, value = pair.split("=")
189
+ key.strip!
190
+ value.strip!
191
+ next unless re_keys.match(key)
192
+ case key.downcase
193
+ when "host"
194
+ @hosts.push(value)
195
+ when "port"
196
+ @ports.push(value.to_i)
197
+ when "sid"
198
+ @sid = value
199
+ end
200
+ }
201
+ end
202
+
203
+ alias :ping_listener? :ping?
204
+ end
205
+ end
@@ -0,0 +1,112 @@
1
+ #########################################
2
+ # tc_tns_ping.rb
3
+ #
4
+ # Test suite for the tns-ping package.
5
+ #########################################
6
+ base = File.basename(Dir.pwd)
7
+
8
+ if base == "test" || base =~ /net-tnsping.*/
9
+ Dir.chdir("..") if base == "test"
10
+ $LOAD_PATH.unshift(Dir.pwd)
11
+ $LOAD_PATH.unshift(Dir.pwd + "/lib")
12
+ end
13
+
14
+ require "test/unit"
15
+ require "net/tnsping"
16
+ include Net
17
+
18
+ # NOTE:
19
+ # You'll need to change the db and host if you want to run these tests.
20
+
21
+ class TC_TNSPing < Test::Unit::TestCase
22
+ def setup
23
+ @db = "changeme"
24
+ @tp = TNSPing.new(@db)
25
+ end
26
+
27
+ def test_version
28
+ assert_equal("1.0.0", TNSPing::VERSION)
29
+ end
30
+
31
+ def test_db
32
+ assert_respond_to(@tp, :db)
33
+ assert_respond_to(@tp, :db=)
34
+ assert_nothing_raised{ @tp.db }
35
+ assert_equal(@db, @tp.db)
36
+ end
37
+
38
+ def test_dsn
39
+ assert_respond_to(@tp, :dsn)
40
+ assert_respond_to(@tp, :dsn=)
41
+ end
42
+
43
+ def test_port
44
+ assert_respond_to(@tp, :port)
45
+ assert_respond_to(@tp, :port=)
46
+ assert_equal(1521, @tp.port)
47
+ end
48
+
49
+ def test_ports
50
+ assert_respond_to(@tp, :ports)
51
+ assert_kind_of(Array, @tp.ports)
52
+ assert(@tp.ports.length > 0)
53
+ end
54
+
55
+ def test_host
56
+ assert_respond_to(@tp, :host)
57
+ assert_respond_to(@tp, :host=)
58
+ end
59
+
60
+ def tests_hosts
61
+ assert_respond_to(@tp, :hosts)
62
+ assert_kind_of(Array, @tp.hosts)
63
+ assert(@tp.hosts.length > 0)
64
+ end
65
+
66
+ def test_tns_file
67
+ assert_respond_to(@tp, :tns_file)
68
+ assert_respond_to(@tp, :tns_file=)
69
+ assert(File.exist?(@tp.tns_file))
70
+ end
71
+
72
+ def test_oracle_home
73
+ assert_respond_to(@tp, :oracle_home)
74
+ assert_respond_to(@tp, :oracle_home=)
75
+ assert_kind_of(String, @tp.oracle_home)
76
+ end
77
+
78
+ def test_tns_admin
79
+ assert_respond_to(@tp, :tns_admin)
80
+ assert_respond_to(@tp, :tns_admin=)
81
+ end
82
+
83
+ def test_ping_listener
84
+ assert_respond_to(@tp, :ping?)
85
+ assert_nothing_raised{ @tp.ping? }
86
+ assert_equal(true, @tp.ping?)
87
+ end
88
+
89
+ def test_ping_database
90
+ assert_respond_to(@tp, :ping_database?)
91
+ assert_nothing_raised{ @tp.ping_database? }
92
+ assert_equal(true, @tp.ping_database?)
93
+ end
94
+
95
+ def test_ping_all
96
+ assert_respond_to(@tp, :ping_all?)
97
+ assert_nothing_raised{ @tp.ping_all? }
98
+ assert_equal(true, @tp.ping_all?)
99
+ end
100
+
101
+ def test_bad_db
102
+ bad_db = "foo123bar"
103
+ assert_raises(TNSPingError){ TNSPing.new(bad_db) }
104
+ @tp.port = 9999
105
+ assert_equal(false, @tp.ping_listener?)
106
+ end
107
+
108
+ def teardown
109
+ @db = nil
110
+ @tp = nil
111
+ end
112
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.10
3
+ specification_version: 1
4
+ name: net-tnsping
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2005-06-07
8
+ summary: A package for pinging Oracle listeners and databases
9
+ require_paths:
10
+ - lib
11
+ email: djberg96@gmail.com
12
+ homepage: http://www.rubyforge.org/projects/shards
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ authors:
27
+ - Daniel J. Berger
28
+ files:
29
+ - lib/net/tnsping.rb
30
+ - CHANGES
31
+ - MANIFEST
32
+ - README
33
+ - test/tc_tns_ping.rb
34
+ test_files:
35
+ - test/tc_tns_ping.rb
36
+ rdoc_options: []
37
+
38
+ extra_rdoc_files:
39
+ - README
40
+ - CHANGES
41
+ executables: []
42
+
43
+ extensions: []
44
+
45
+ requirements: []
46
+
47
+ dependencies: []
48
+