net-tnsping 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+