1 | #!/usr/bin/ruby1.8 -w
|
---|
2 |
|
---|
3 | ###
|
---|
4 | # lb9a.rb
|
---|
5 | # libraries that behave as a "wrapper" to XorPlus on the LB9A (Quanta) switch.
|
---|
6 | # 8/28/11 - added dry-run capability
|
---|
7 | # 8/29/11 - string/array processing functions as class extensions; some cleanup of Session class
|
---|
8 | ###
|
---|
9 |
|
---|
10 | require 'net/telnet'
|
---|
11 | require 'singleton'
|
---|
12 | require 'logger'
|
---|
13 |
|
---|
14 | LOG = Logger.new(STDOUT)
|
---|
15 |
|
---|
16 | $portrng = (1..48) #48 GBe ports on switch
|
---|
17 | $vlanrng = (1..1024) #up to 1024 VLAN IDs per switch
|
---|
18 |
|
---|
19 |
|
---|
20 | # Deals with telnet session/CLI interactions
|
---|
21 | class Session
|
---|
22 |
|
---|
23 | attr_reader :hname, :port, :dbg
|
---|
24 |
|
---|
25 | def initialize(hostname, port = 23, debug = false, dryrun = false)
|
---|
26 | @hname = hostname
|
---|
27 | @port = port
|
---|
28 | @dbg = debug #verbose mode
|
---|
29 | @drun = dryrun #do not actually send command via serial
|
---|
30 | end
|
---|
31 |
|
---|
32 | # init - initialize telnet session
|
---|
33 | def init()
|
---|
34 | begin
|
---|
35 | if @dbg then LOG.level = Logger::DEBUG else LOG.level = Logger::INFO end
|
---|
36 | unless @drun
|
---|
37 | @conn = Net::Telnet::new( "Host" => @hname, "Port" => @port )
|
---|
38 | @conn.cmd("configure") #enter config mode
|
---|
39 | LOG.debug("Session.init: initializing telnet session")
|
---|
40 | else
|
---|
41 | @conn = nil
|
---|
42 | LOG.debug("Session.init: initializing dry-run")
|
---|
43 | end
|
---|
44 | rescue Exception => e
|
---|
45 | LOG.fatal("Session.init: #{e.class}, #{e.message}")
|
---|
46 | raise
|
---|
47 | end
|
---|
48 | end
|
---|
49 |
|
---|
50 | private
|
---|
51 | # rncmd - send command for each port in iflist, replacing '##' in command with proper port number
|
---|
52 | # cannot take commands with a scrolling output buffer (To-do)
|
---|
53 | def rncmd(command, iflist)
|
---|
54 | begin
|
---|
55 | LOG.debug("Session.range_cmd: command: #{command}")
|
---|
56 | LOG.debug("Session.range_cmd: iflist: #{iflist}")
|
---|
57 | iflist.map do |iface|
|
---|
58 | out = command.gsub(/##/, iface.to_s)
|
---|
59 | unless @drun then ret = @conn.cmd(out) else puts "#{out}" end
|
---|
60 | LOG.debug("Session.range_cmd: command: #{out}")
|
---|
61 | LOG.debug("Session.range_cmd: return: #{ret}")
|
---|
62 | end
|
---|
63 | rescue Exception => e
|
---|
64 | LOG.fatal("Session.rncmd: #{e.class} #{e.message}")
|
---|
65 | raise
|
---|
66 | end
|
---|
67 | end
|
---|
68 |
|
---|
69 | public
|
---|
70 | # set_iface - sets management interface address
|
---|
71 | def set_iface(iface, addr, del = false)
|
---|
72 | begin
|
---|
73 | if del then dflg = "delete " else dflg = "set " end
|
---|
74 | if (iface.match(/eth(0|1)/))&&(addr.match(/\d+\.\d+\.\d+\.\d+\/\d+/))
|
---|
75 | out = "#{dflg}interface management-ethernet #{iface} address #{addr}"
|
---|
76 | unless @drun
|
---|
77 | @conn.cmd(out) do |ret|
|
---|
78 | LOG.debug("Session.set_iface: return: #{ret}")
|
---|
79 | end
|
---|
80 | else
|
---|
81 | puts "#{out}"
|
---|
82 | end
|
---|
83 | return true
|
---|
84 | else
|
---|
85 | puts "\tiface [eth0|eth1] [IP addr/mask]"
|
---|
86 | return false
|
---|
87 | end
|
---|
88 | rescue Exception => e
|
---|
89 | LOG.fatal("Session.set_iface: #{e.class} #{e.message}")
|
---|
90 | raise
|
---|
91 | end
|
---|
92 | end
|
---|
93 |
|
---|
94 | # set_vlan - sets up a new VLAN
|
---|
95 | # To-do : add multiple VLAN delete facility
|
---|
96 | def set_vlan(id, del = false)
|
---|
97 | begin
|
---|
98 | if del then dflg = "delete " else dflg = "set " end
|
---|
99 | if ($vlanrng.member? id)
|
---|
100 | out = "#{dflg}vlans vlan-id #{id}"
|
---|
101 | unless @drun
|
---|
102 | @conn.cmd(out) do |ret|
|
---|
103 | LOG.debug("Session.set_vlan: return: #{ret}")
|
---|
104 | end
|
---|
105 | else
|
---|
106 | puts "#{out}"
|
---|
107 | end
|
---|
108 | return true
|
---|
109 | else
|
---|
110 | puts "\tvlan [VLAN ID]"
|
---|
111 | puts "\tVLAN ID must be value between [1..1024]"
|
---|
112 | return false
|
---|
113 | end
|
---|
114 | rescue Exception => e
|
---|
115 | LOG.fatal("Session.set_vlan: #{e.class} #{e.message}")
|
---|
116 | raise
|
---|
117 | end
|
---|
118 | end
|
---|
119 |
|
---|
120 | # set_ports - configurations of various port properties
|
---|
121 | def set_ports(ctxt, iflist)
|
---|
122 | @vlans = []
|
---|
123 | @tflag = false
|
---|
124 | @cmd = ' '
|
---|
125 |
|
---|
126 | args = ctxt.split(' ') # args - array containing components of command
|
---|
127 | LOG.debug("Session.set_ports: args: #{args[0]},#{args[1]}")
|
---|
128 |
|
---|
129 | # assign the proper command to @cmd based on input
|
---|
130 | case args[0]
|
---|
131 | when "delete"
|
---|
132 | @cmd = "delete interface gigabit-ethernet ge-1/1/## family ethernet-switching"
|
---|
133 | when "vlan"
|
---|
134 | if ($vlanrng.member? args[1].to_i)
|
---|
135 | @cmd = "set interface gigabit-ethernet ge-1/1/## family ethernet-switching native-vlan-id #{args[1]}"
|
---|
136 | else
|
---|
137 | puts "\tVLAN ID must be value between [1..1024]"
|
---|
138 | end
|
---|
139 | when "mode"
|
---|
140 | if args[1] == "access"
|
---|
141 | @cmd = "set interface gigabit-ethernet ge-1/1/## family ethernet-switching port-mode access"
|
---|
142 | elsif args[1] == "trunk"
|
---|
143 | @tflag = true
|
---|
144 | print "Member VLANs (X,Y,Z...): "
|
---|
145 | @vlans = gets.chomp.split(',')
|
---|
146 |
|
---|
147 | LOG.debug("Session.set_ports: vlans #{@vlans.class}: #{@vlans}")
|
---|
148 | @cmd = "set interface gigabit-ethernet ge-1/1/## family ethernet-switching port-mode trunk"
|
---|
149 | else
|
---|
150 | puts "\t#{args[1]}: Unknown port mode"
|
---|
151 | end
|
---|
152 | else
|
---|
153 | puts "\t#{ctxt}: Unknown command"
|
---|
154 | end
|
---|
155 | LOG.debug("Session.set_ports: @cmd: #{@cmd}")
|
---|
156 |
|
---|
157 | # invoke rncmd with proper command
|
---|
158 | if @tflag
|
---|
159 | for i in @vlans
|
---|
160 | rncmd("set interface gigabit-ethernet ge-1/1/## family ethernet-switching vlan members #{i}",iflist)
|
---|
161 | end
|
---|
162 | end
|
---|
163 |
|
---|
164 | unless @cmd.empty?
|
---|
165 | rncmd(@cmd, iflist)
|
---|
166 | return true
|
---|
167 | else
|
---|
168 | return false
|
---|
169 | end
|
---|
170 | end
|
---|
171 |
|
---|
172 | # commit - saves configurations
|
---|
173 | def commit()
|
---|
174 | puts "saving settings (this may take a while)..."
|
---|
175 | unless @drun then @conn.cmd("commit") else puts "commit" end
|
---|
176 | sleep 5
|
---|
177 | unless @drun then @conn.cmd("save running-to-startup") else puts "save running-to-startup" end
|
---|
178 | end
|
---|
179 |
|
---|
180 | # logoff - close connection after exiting enabled mode
|
---|
181 | def logoff(saved)
|
---|
182 | begin
|
---|
183 | unless @drun
|
---|
184 | unless saved then @conn.cmd("exit discard") end
|
---|
185 | @conn.cmd("exit")
|
---|
186 | @conn.close
|
---|
187 | else
|
---|
188 | puts "exit"
|
---|
189 | end
|
---|
190 | rescue Exception => e
|
---|
191 | LOG.fatal("Session.logoff: #{e.class} #{e.message}")
|
---|
192 | raise
|
---|
193 | end
|
---|
194 | end
|
---|
195 |
|
---|
196 | end
|
---|
197 |
|
---|
198 | # to_rng - flattens nested arrays with ranges e.g. [3..5,20,[13..15]] -> [3,4,5,20,13,14,15]
|
---|
199 | class Array
|
---|
200 | def to_rng()
|
---|
201 | self.map do |x|
|
---|
202 | if x.is_a? Range
|
---|
203 | x.to_a
|
---|
204 | elsif x.is_a? Fixnum
|
---|
205 | x
|
---|
206 | elsif x.is_a? Array
|
---|
207 | x.to_rng
|
---|
208 | else
|
---|
209 | raise "Array.to_rng:#{x}:not Fixnum or Range"
|
---|
210 | end
|
---|
211 | end.flatten
|
---|
212 | end
|
---|
213 | end
|
---|
214 |
|
---|
215 | # to_ra - convers string of numbers and rangesto array with Range and Fixnum objects e.g. "1..3,5" -> [1..3,5]
|
---|
216 | class String
|
---|
217 | def to_ra()
|
---|
218 | self.split(',').map do |x|
|
---|
219 | if x.length <= 2
|
---|
220 | Integer(x)
|
---|
221 | elsif x.include? ".."
|
---|
222 | ends = x.split("..").map{|i| Integer(i)}
|
---|
223 | x = ends[0]..ends[1]
|
---|
224 | else
|
---|
225 | raise "Sring.to_ra:#{x}:Invalid range string format"
|
---|
226 | end
|
---|
227 | end
|
---|
228 | end
|
---|
229 | end
|
---|
230 |
|
---|
231 | =begin
|
---|
232 | # the set_* family of commands without subcontexts follow this pattern...
|
---|
233 | # If functions can be used as input, this is a legitimate template.
|
---|
234 | def set_ptype(name, matchstr, cmd, syntax)
|
---|
235 | begin
|
---|
236 | if (matchstr)
|
---|
237 | out = cmd
|
---|
238 | unless @drun
|
---|
239 | @conn.cmd(out) do |ret|
|
---|
240 | LOG.debug("Session.#{name}: return: #{ret}")
|
---|
241 | end
|
---|
242 | else
|
---|
243 | puts "#{out}"
|
---|
244 | end
|
---|
245 | else
|
---|
246 | puts "#{syntax}"
|
---|
247 | end
|
---|
248 | rescue Exception => e
|
---|
249 | LOG.fatal("Session.set_iface: #{e.class} #{e.message}")
|
---|
250 | raise
|
---|
251 | end
|
---|
252 | end
|
---|
253 | =end
|
---|