/auiserver/new_server.rb
Ruby | 763 lines | 607 code | 79 blank | 77 comment | 52 complexity | c011ce4b9f45afed8197071dd1f5b639 MD5 | raw file
- =begin license
- * Copyright (c) 2006-2008, Fused Solutions, LLC
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of the Fused Solutions, LLC nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY Fused Solutions, LLC ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL Fused Solutions, LLC BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- =end
- $0 = 'auiserver'
- $LOAD_PATH << File.dirname(__FILE__)
- require 'thread'
- require 'mysql'
- require 'timeout'
- require 'uri'
- Thread.abort_on_exception = true
- load 'version.rb'
- begin
- load 'config/server.conf'
- rescue LoadError => e
- STDERR.puts "Error when loading server config file, does config/server.conf exist?"
- STDERR.puts "Error message: #{e.message}"
- exit(1)
- rescue SyntaxError => e
- STDERR.puts "Syntax error encountered when loading config/server.conf"
- STDERR.puts "Error message: #{e.message}"
- exit(2)
- end
- require 'constants/protocol'
- require 'cpxlog'
- Cpxlog.open
- require 'auth'
- require 'cdr'
- require 'agents'
- require 'queues'
- require 'server_events'
- require 'client_events'
- class SqlConnection
- def initialize
- begin
- connect
- rescue MysqlError => e
- Cpxlog.err("#{e.errno} #{e.error}")
- exit(1)
- end
- end
-
- def safe_query(querystring)
- retries = 0
- begin
- @dbh.query(querystring)
- rescue Mysql::Error => e
- case e.errno
- when 2006 # MySQL timeout error code
- if connect and retries == 0
- Cpxlog.warning("MySQL connection timed out, trying to reconnect #{e.errno} #{e.error}")
- connect
- retries += 1
- retry #try again
- else
- Cpxlog.err("#{e.errno} #{e.error}")
- puts e.backtrace
- exit(1)
- end
- else
- Cpxlog.err("#{e.errno} #{e.error} -- #{querystring}")
- puts e.backtrace
- exit(1)
- end
- end
- end
- def connect
- @dbh = Mysql.real_connect(ServerConfig::MYSQL_CREDENTIALS[:host],
- ServerConfig::MYSQL_CREDENTIALS[:username],
- ServerConfig::MYSQL_CREDENTIALS[:password],
- ServerConfig::MYSQL_CREDENTIALS[:database])
- end
- def quote(string)
- @dbh.quote(string.to_s)
- end
- end
- class Server
- def self.new(*args)
- if @instance
- raise RuntimeError, "Only one server can be run at a time", caller
- end
- @instance = super(*args)
- end
- def self.instance
- @instance
- end
- attr_reader :startuptime
- def initialize
- @startuptime = Time.now
- @sockets = []
- @salt = {}
- end
- def connect
- Cpxlog.info "Welcome to CPX Agent Server version #{VERSIONSTRING}"
- @eventserver = EventServer.new(ServerConfig::AMI_CREDENTIALS[:host], ServerConfig::AMI_CREDENTIALS[:port])
- @eventserver.connect
- banner = @eventserver.socket.gets
- unless banner.chomp.include?('CBX')
- STDERR.puts "Invalid CBX banner received: \"#{banner.chomp}\", disconnecting"
- exit(-1)
- end
- Cpxlog.info "Successfully connected to CBX at #{ServerConfig::AMI_CREDENTIALS[:host]}"
- @dbh = SqlConnection.new
- CDR.connect(@dbh)
- @auth = MySQLAuth.new(@dbh)
- Agent.sql = @dbh
- EventServer.dbh = @dbh
- # request bootstrapping info
- @eventserver.send_event('Login', 'Username'=>ServerConfig::AMI_CREDENTIALS[:username], 'Password'=>ServerConfig::AMI_CREDENTIALS[:password]) do |reply|
- if reply.success?
- @eventserver.send_event('Agents') do |reply|
- if reply.success?
- @eventserver.send_event('QueueStatus') do |reply|
- if reply.success?
- # use the response to the ping event to determine when the boostrapping is done
- @eventserver.send_event('Uptime') do |reply|
- if reply.success?
- @eventserver.startuptime = Time.at(reply.startuptime.to_i)
- Cpxlog.info("CBX startuptime is #{@eventserver.startuptime}")
- CDR.handle_orphaned(@eventserver)
- @clientserver = TCPServer.new(ServerConfig::LISTEN_HOST, ServerConfig::LISTEN_PORT)
- accept_connections #more or less ready to listen for clients
- Cpxlog.info("Ready for action!")
- else
- Cpxlog.err("'Uptime' AMI event failed?!: @#{ServerConfig::AMI_CREDENTIALS[:host]}:#{ServerConfig::AMI_CREDENTIALS[:port]} #{reply.message}")
- exit(1)
- end
- end
- else
- Cpxlog.err("'QueueStatus' AMI event failed: @#{ServerConfig::AMI_CREDENTIALS[:host]}:#{ServerConfig::AMI_CREDENTIALS[:port]} #{reply.message}")
- exit(1)
- end
- end
- else
- Cpxlog.err("'Agents' AMI event failed: @#{ServerConfig::AMI_CREDENTIALS[:host]}:#{ServerConfig::AMI_CREDENTIALS[:port]} #{reply.message}")
- exit(1)
- end
- end
- else
- Cpxlog.err("Failed to connect to the AMI @#{ServerConfig::AMI_CREDENTIALS[:host]}:#{ServerConfig::AMI_CREDENTIALS[:port]} #{reply.message}")
- exit(1)
- end
- end
- listen_input
- end
- def accept_connections
- Thread.new do
- loop do
- socket = @clientserver.accept
- remoteaddr = socket.peeraddr[2]
- begin
- Timeout.timeout(5) do
- socket.puts "Agent Server: #{VERSIONSTRING}"
- end
- Timeout.timeout(5) do
- line = socket.gets
- next if line.nil?
- if md = /^Protocol: ([1-9])\.([0-9]+)(.*)$/.match(line.chomp)
- if AgentProtocol::MAJOR != md[1].to_i
- socket.puts "2 Protocol major version mismatch. Login denied"
- Cpxlog.notice "Client connecting from #{socket.peeraddr[2]} is using an incompatible client"
- socket.close
- elsif AgentProtocol::MINOR > md[2].to_i
- socket.puts "1 You are using an outdated client. Please visit #{ServerConfig::AGENTUIURL} to upgrade your client"
- Cpxlog.notice "Client connecting from #{socket.peeraddr[2]} is using an outdated client"
- elsif AgentProtocol::MINOR < md[2].to_i
- socket.puts "1 Protocol minor version mismatch. Please consider downgrading your client"
- Cpxlog.notice "Client connecting from #{socket.peeraddr[2]} is using an client newer than the server"
- else
- socket.puts "0 OK #{ServerConfig::CRMURL}"
- end
- else
- socket.puts "2 Invalid Response. Login denied"
- Cpxlog.notice "Client connecting from #{socket.peeraddr[2]} sent an invalid response to the version string: '#{line.chomp}'"
- socket.close
- end
- end
- rescue Errno::ECONNRESET, Errno::EPIPE, IOError => e
- Cpxlog.warning("Failed to send versionstring to newly connected client(#{remoteaddr}): #{e.class.name}: #{e.message}")
- socket.close
- rescue Timeout::Error
- Cpxlog.warning("Failed to send versionstring to newly connected client #{remoteaddr} (timed out after 5 seconds)")
- socket.close
- else
- @sockets << socket
- end
- end
- end
- end
- def listen_input
- loop do
- sockets = (@sockets+[@eventserver.socket]).compact
-
- sockets.select{|x| x.closed?}.each do |x|
- @sockets.delete x
- @salt.delete x
- end
- sockets = sockets.reject{|x| x.closed?}
- if result = select(sockets, nil, nil , 0.25)
- input, output, err = result
- input.each do |i|
- if i == @eventserver.socket
- handle_server_event(@eventserver)
- next
- end
- begin
- line = nil
- Timeout.timeout(5) do
- line = i.gets
- end
- rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ETIMEDOUT, IOError
- if agent = Agent[i]
- terminate_agent(agent)
- end
- @sockets.delete i
- @salt.delete i
- next
- rescue Timeout::Error
- if agent = Agent[i]
- Cpxlog.err("Agent #{agent} from #{i.peeraddr[2]} timed out after 5 seconds while reading from their socket")
- terminate_agent(agent)
- else
- Cpxlog.err("Unknown connection from #{i.peeraddr[2]} timed out after 5 seconds while reading")
- end
- @sockets.delete i
- @salt.delete i
- next
- end
- if agent = Agent[i] and line
- handle_agent_event(agent, line)
- elsif line
- # unauthenticated agent, we have to get a bit dirty here
- begin
- if line.split(' ', 2)[0] == 'LOGIN'
- login_agent(line, i)
- elsif line.split(' ', 2)[0] == 'GETSALT'
- allocate_salt(line, i)
- else
- counter = line.split(' ')[1]
- i.puts "ERR #{counter} This is an unauthenticated connection, the only permitted actions are GETSALT and LOGIN"
- end
- rescue Errno::ECONNRESET, Errno::EPIPE, IOError
- begin
- remoteaddr = i.peeraddr[2]
- rescue Errno::ENOTCONN
- remoteaddr = "<disconnected>"
- end
- if agent = Agent[i]
- terminate_agent(agent)
- Cpxlog.warning("Agent #{agent} at #{remoteaddr} disconnected before response returned")
- else
- Cpxlog.warning("Unknown connection from #{remoteaddr} disconnected before response returned")
- end
- @sockets.delete i
- @salt.delete i
- end
- else
- #dead connection
- if agent = Agent[i]
- terminate_agent(agent)
- end
- @sockets.delete i
- @salt.delete i
- end
- end
- end
- Agent.match_agents(:online=>true) do |agent|
- agent.handle_unreplied(60) do |event|
- if event[:retries] < 2
- Cpxlog.warning("resending event #{event[:counter]} to #{agent} (#{event[:event]})")
- agent.outputqueue << event[:event]
- event[:retries] += 1
- event[:time] = Time.now
- else
- # expired event
- Cpxlog.warning("Resent event #{event[:counter]} to #{agent} twice and received no reply. Disconnecting agent.")
- #agent.expire_event event
- terminate_agent(agent)
- agent.socket.close unless agent.socket.closed?
- @sockets.delete agent.socket
- @salt.delete agent.socket
- break
- end
- end
- while !agent.outputqueue.empty?
- begin
- agent.socket.puts agent.outputqueue.pop
- rescue Errno::EPIPE, Errno::ECONNRESET, IOError
- Cpxlog.warning("connection reset when sending to #{agent}")
- terminate_agent(agent)
- @sockets.delete agent.socket
- @salt.delete agent.socket
- break
- end
- end
- end
-
- while !@eventserver.outputqueue.empty?
- # send cbx anything queued for it
- x = @eventserver.outputqueue.pop
- begin
- @eventserver.socket.puts x
- rescue Errno::ECONNRESET, Errno::EPIPE, IOError
- Cpxlog.err "Connection to CBX terminated unexpectedly when writing"
- exit(1)
- end
- end
- end
- end
- def handle_server_event(eventserver)
- begin
- # pull a line out of the queue for processing
- line = eventserver.socket.gets("\r\n\r\n")
- rescue Errno::ECONNRESET, Errno::EPIPE, IOError
- # NOTE-- The eventserver is busted.
- Cpxlog.err "Connection to CBX terminated unexpectedly when reading"
- exit(1)
- end
- if line
- begin
- event = ServerEventFactory.eventfactory(eventserver, line)
- rescue ServerEvent::UnknownEventError => e
- # Cpxlog.warning("Received unknown event from AMI; #{e.message} (#{e.backtrace[0]})")
- else
- begin
- event.dispatch
- rescue ServerEvent::InvalidEventError => e
- Cpxlog.warning "Received invalid event from CBX; #{e.message} (#{e.backtrace[1]})"
- end
- end
- else
- # NOTE-- eventserver died horribly.
- if eventserver.shutdown?
- Cpxlog.notice "Connection to CBX closed"
- exit
- else
- Cpxlog.err "Connection to CBX terminated unexpectedly"
- exit(1)
- end
- end
- end
- def handle_agent_event(agent, line)
- # try to create the event
- begin
- event = ClientEventFactory.eventfactory(agent, @eventserver, line.chomp)
- rescue ClientEvent::InsufficientPermissionError => e
- counter = line.split(' ')[1]
- agent.send_err(counter, e.message)
- Cpxlog.warning "Agent #{agent} tried to execute '#{line.chomp}' but they lack permissions to do so"
- rescue ClientEvent::UnknownEventError => e
- counter = line.split(' ')[1]
- agent.send_err(counter, e.message)
- Cpxlog.warning "Agent #{agent} sent an unknown event: '#{line.chomp}'"
- rescue ClientEvent::IllegalEventError => e
- #there's no counter, we're kinda hosed...
- agent.send_err(1, e.message)
- Cpxlog.warning "Agent #{agent} sent an illegal event: '#{line.chomp}'; #{e.message}"
- else
- # try dispatching the event
- begin
- event.dispatch
- rescue ClientEvent::InvalidEventError => e
- agent.send_err(event.counter, e.message)
- Cpxlog.warning "Agent #{agent} sent an invalid event: '#{line.chomp}'; #{e.message}"
- else
- agent.send_ack(event.counter, event.retdata) if event.ack?
- end
- end
- end
- def allocate_salt(line, socket)
- salt = rand(4294967295) # 32 bit random number
- command, counter, data = line.strip.split(' ', 3)
- begin
- counter = Integer(counter)
- rescue ArgumentError
- socket.puts "ERR invalid counter #{counter}"
- return
- end
- socket.puts("ACK #{counter} #{salt}")
- @salt[socket] = salt
- end
- def login_agent(line, socket)
- command, counter, data = line.strip.split(' ', 3)
- begin
- counter = Integer(counter)
- rescue ArgumentError
- socket.puts "ERR invalid counter #{counter}"
- return
- end
- unless @salt[socket]
- socket.puts "ERR #{counter} Please request a salt with GETSALT first"
- return
- end
- logincreds, remotenumber = data.split(' ', 2)
- username, password = logincreds.split(':', 2)
- if username and password and credentials = @auth.authenticate(username, password, @salt[socket])
- credentials[:id] = credentials[:id]+ServerConfig::BASEID
-
- agent = Agent[credentials[:id]]
- if remotenumber and !remotenumber.empty?
- if [4, 7, 10, 11].include? remotenumber.length and remotenumber =~ /^[0-9]+$/
- remdial = "${GLOBAL(TRUNK)}/#{remotenumber}"
- elsif remotenumber =~ /^([\w\d\.]+)\:([0-9]+)$/
- remdial = "SIP/#{remotenumber}"
- else
- socket.puts "ERR #{counter} Remote number must be 4, 7, 10 or 11 digits and fully numeric (not #{remotenumber})"
- return
- end
- else
- remdial = false
- end
- if agent and agent.socket
- # TODO: ping agent, are they still up and working?
- Cpxlog.warning "Agent #{agent} is already marked as logged in"
- socket.puts "ERR #{counter} This agent is already logged in"
- else
- queues = []
- @eventserver.send_event('AgentCallbackLogin', {'Agent'=>credentials[:id], 'Exten'=>credentials[:id], 'Context'=>'sip-agents', 'AckCall'=>ServerConfig::ACKCALL.to_s, 'WrapupTime'=>'0'}) do |reply|
- if reply.success?
- # NOTE: there is a possible inconsistancy in the agent name depending on if the agent
- # was populated from the agent list or is newly created. If the agent was created from
- # the agent list from the AMI, the agent name will be the one in the agent name table,
- # otherwise the name is the name used to login (the one from the authentication table)
- unless agent
- agent = Agent.new(@eventserver, credentials[:id], socket)
- agent.name = username
- else
- agent.set_socket socket
- end
-
- # populate agent dialplan
- # TODO - remote agent callback authentication...?
- if ! remdial
- diallocation = "SIP/#{username}"
- else
- diallocation = remdial
- end
- =begin
- if remotenumber and !remotenumber.empty?
- diallocation = "${GLOBAL(TRUNK)}/#{remotenumber}"
- else
- diallocation = "SIP/#{username}"
- end
- =end
- @dbh.safe_query("DELETE FROM extensions WHERE context='sip-agents' AND exten='#{credentials[:id]}'")
- @dbh.safe_query("INSERT INTO extensions SET context='sip-agents', exten='#{credentials[:id]}', priority=1, app='Set', appdata='REALBRIDGEPEER=Agent/#{agent.id}' ")
- @dbh.safe_query("INSERT INTO extensions SET context='sip-agents', exten='#{credentials[:id]}', priority=2, app='Dial', appdata='#{diallocation}' ")
- @dbh.safe_query("INSERT INTO extensions SET context='sip-agents', exten='#{credentials[:id]}', priority=3, app='Hangup'")
- agent.seclevel = credentials[:seclevel]
- begin
- #agent.set_defaultprofile(credentials[:defaultprofile])
- queues = agent.set_defaultprofile(credentials[:defaultprofile])
- rescue Agent::InvalidProfileError
- # TODO - infer a sane default profile?
- Cpxlog.warning "Agent #{agent} has an invalid default profile #{credentials[:defaultprofile]}"
- socket.puts 'ERR #{counter} Your default profile is invalid, please notify the supervisor'
- end
- socket.puts "ACK #{counter} #{agent.seclevel} #{agent.profile} #{Time.now.to_i}"
- @eventserver.send_event('QueuesSync', {'Interface'=>"Agent/#{agent.id}"}) do |reply|
- if reply.success?
- agent.pause(true) do
- #if agent.talkingto
- # XXX this is kind of a hack
- #agent.state = Agent::ONCALL
- #agent.state = Agent::WARMXFER
- #else
- agent.state = Agent::RELEASED
- #end
- end
- else
- Cpxlog.warning "FAILED QUEUESSYNC?"
- end
- end
- Cpxlog.info "Agent #{agent} logged in from #{agent.socket.peeraddr[2]}"
- elsif reply.message =~ /^No such agent/
- socket.puts "ERR #{counter} This Agent is unknown to CBX"
- Cpxlog.warning "Agent #{agent} authenticated to the agent server, but failed to authenticate to CBX"
- throw :return
- elsif reply.message =~ /Agent already logged in/
- # Agent is still logged into cbx - AUIserver restart or
- # agent hungup in-call and is reconnecting before the call is done
- unless agent
- agent = Agent.new(@eventserver, credentials[:id], socket)
- agent.name = username
- else
- agent.set_socket socket
- end
- agent.seclevel = credentials[:seclevel]
- #agent.defaultprofile = credentials[:defaultprofile]
- if agent.profile != 0
- queues = agent.set_defaultprofile(agent.profile) # fix hosed queue memberships
- else
- # TODO - possibly use the states table to get the previous profile?
- queues = agent.set_defaultprofile(credentials[:defaultprofile])
- end
- @eventserver.send_event('QueuesSync', {'Interface'=>"Agent/#{agent.id}"}) do |reply|
- if !reply.success?
- Cpxlog.warning "FAILED QUEUESSYNC?"
- end
- end
- if agent.talkingto
- socket.puts "ACK #{counter} #{agent.seclevel} #{agent.profile} #{Time.now.to_i}"
- agent.pause(true) do
- # This isn't perfect, but it's worth a try to recover calltype this way
- if agent.talkingto.type == :outgoing
- agent.state = Agent::OUTGOINGCALL
- else
- agent.state = Agent::ONCALL
- end
- cid = URI.escape(agent.talkingto.get('CALLERIDNAME') + " <" + agent.talkingto.get('CALLERIDNUM') + ">")
- agent.send_event('CALLINFO', agent.talkingto.get('BRANDID'), agent.talkingto.type, cid)
- end
- # NOTE : this might conflict with CDR recovery...
- if agent.talkingto.cdr.last_transaction_end
- agent.talkingto.cdr.add_transaction(CDR::INCALL, agent.id, agent.talkingto.cdr.last_transaction_end)
- end
- Cpxlog.notice "Agent #{agent} re-logged in from #{agent.socket.peeraddr[2]} and is still in-call"
- elsif call = Caller.callers.detect{|x| x.ringingto == agent.id}
- socket.puts "ACK #{counter} #{agent.seclevel} #{agent.profile} #{Time.now.to_i}"
- agent.pause(false) do
- agent.state = Agent::RINGING
- end
- cid = URI.escape(call.get('CALLERIDNAME') + " <" + call.get('CALLERIDNUM') + ">")
- agent.send_event('CALLINFO', call.get('BRANDID'), call.type, cid)
- Cpxlog.notice "Agent #{agent} re-logged in from #{agent.socket.peeraddr[2]} and is ringing"
- else
- socket.puts "ACK #{counter} #{agent.seclevel} #{agent.profile} #{Time.now.to_i}"
- Cpxlog.warning("agent #{agent} logged in, not talking to anyone after restart?")
- agent.pause(true) do
- agent.state = Agent::RELEASED
- end
- end
- else
- Cpxlog.warning("Failed to login Agent #{credentials[:id]} with error #{reply.message}")
- throw :return
- end
- CallQueue.update_queue_membership(agent, queues)
-
- Agent.match_agents(:seclevel=>2) do |a|
- a.send_event('QUEUEMEMBER', agent.id, agent.name, agent.statetime, agent.state, queues.join(':'))
- end
- end
- end
- else
- Cpxlog.notice "Failed authentication attempt for #{username} from #{socket.peeraddr[2]}"
- socket.puts "ERR #{counter} invalid credentials for #{username}"
- end
- end
- def terminate_agent(agent)
- return unless agent.socket # don't bother if this agent already got nuked
- oldsocket = agent.socket # save socket so we can return it to finish logging out
- agent.set_socket(nil) # then nil socket so subsequent call to this method return above,
- # and prevent events being sent to an agent about to be logged off
- if agent.state == Agent::WRAPUP and agent.talkingto
- Cpxlog.warning("Completing wrapup for #{agent.talkingto} on agent #{agent} logoff")
- # Ensure we only write ENDWRAPUP if we have an unterminated INWRAPUP
- agent.talkingto.cdr.add_transaction(CDR::ENDWRAPUP, agent.id) if agent.talkingto.cdr.get_last_unterminated_transaction(CDR::ENDWRAPUP, agent.id)
- # check for other unterminated wrapups here!
- if t = agent.talkingto.cdr.get_last_unterminated_transaction(nil, nil)
- Cpxlog.warning "Not writing CDREND for #{agent.talkingto} because there are other open transactions (#{CDR::TRANSACTIONNAMES[t[:transaction]]} started at #{Time.at(t[:start])} with data: #{t[:data]})"
- else
- agent.talkingto.cdr.add_transaction(CDR::CDREND)
- end
- agent.talkingto = nil
- end
- @eventserver.send_event('AgentLogoff', {'Agent'=>agent.id, 'Soft'=>'true'}) do |reply|
- if reply.success?
- agent.logoff
- else
- Cpxlog.warning("Failed to logoff agent #{agent} : #{reply.message}")
- end
- end
- agent.set_socket(oldsocket,false)
- end
- end
- Server.new
- trap('SIGTERM') do
- puts caller
- # cleanup and exit
- Cpxlog.notice "Shutdown requested via the TERM signal"
- Cpxlog.notice "Logging off all agents.."
- Agent.agents.each do |x|
- Server.instance.terminate_agent(x)
- end
- Cpxlog.notice '..done, exiting'
- exit
- end
- trap('SIGINT') do
- puts caller
- # cleanup and exit
- Cpxlog.notice "Shutdown requested via the INT signal"
- Cpxlog.notice "Logging off all agents.."
- Agent.agents.each do |x|
- Server.instance.terminate_agent(x)
- end
- Cpxlog.notice '..done, exiting'
- exit
- end
- trap('SIGHUP') do
- # reload config (is this worth implementing?)
- end
- trap('SIGUSR1') do
- # reload code
- Cpxlog.notice 'Reloading code...'
- version = Object.send(:remove_const, :VERSIONSTRING)
- begin
- load('version.rb')
- rescue LoadError, SyntaxError => e
- Cpxlog.err("version.rb encountered a #{e.class} when reloading")
- VERSIONSTRING = version
- end
- agentconstants = Object.send(:remove_const, :AgentConstants)
- begin
- load('constants/agentconstants.rb')
- rescue LoadError, SyntaxError => e
- Cpxlog.err("constants/agentconstants.rb encountered a #{e.class} when reloading")
- AgentConstants = agentconstants
- end
- cdrconstants = Object.send(:remove_const, :CDRConstants)
- begin
- load('constants/cdrconstants.rb')
- rescue LoadError, SyntaxError => e
- Cpxlog.err("constants/cdrconstants.rb encountered a #{e.class} when reloading")
- CDRConstants = cdrconstants
- end
- consts = {}
- Cpxlog.constants.each do |const|
- consts[const] = Cpxlog.send(:remove_const, const)
- end
- begin
- load('cpxlog.rb')
- rescue LoadError, SyntaxError => e
- consts.each do |k, v|
- Cpxlog.send(:const_set, k, v)
- end
- Cpxlog.err("cpxlog.rb encountered a #{e.class} when reloading")
- end
- %w{agents.rb queues.rb cdr.rb client_events.rb server_events.rb}.each do |file|
- begin
- load(file)
- rescue SyntaxError, LoadError => e
- Cpxlog.err("#{file} encountered a #{e.class} when reloading")
- end
- end
- #puts 'done'
- Cpxlog.notice '..done reloading code'
- end
- trap('SIGUSR2') do
- puts caller
- # toggle debugging
- end
- begin
- trap('SIGINFO') do
- Cpxlog.increment_log_level
- end
- rescue ArgumentError
- Cpxlog.warning "SIGINFO is not supported on this platform"
- begin
- trap('SIGPWR') do
- Cpxlog.increment_log_level
- end
- rescue ArgumentError
- Cpxlog.warning "SIGPWR is also unsupported on this platform"
- else
- Cpxlog.notice "Use SIGPWR to change log level instead"
- end
- end
- Server.instance.connect