Initial Naily implementation
This commit is contained in:
parent
b03db72118
commit
81972e9da7
67
naily/bin/nailyd
Executable file
67
naily/bin/nailyd
Executable file
@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'naily/server'
|
||||
require 'naily/version'
|
||||
|
||||
require 'ostruct'
|
||||
require 'optparse'
|
||||
|
||||
options = OpenStruct.new
|
||||
options.show_version = false
|
||||
options.daemonize = false
|
||||
options.config_path = nil
|
||||
options.log_path = nil
|
||||
options.log_level = :error
|
||||
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = 'Usage: nailyd [options]'
|
||||
|
||||
opts.separator ''
|
||||
opts.separator 'Options:'
|
||||
|
||||
opts.on('-v', '--version', 'Show version') { |flag| options.show_version = true }
|
||||
opts.on('-d', '--[no-]deamonize', 'Daemonize server') { |flag| options.daemonize = flag }
|
||||
opts.on('-c', '--config PATH', 'Use custom config file') do |path|
|
||||
unless File.exists?(path)
|
||||
puts "Error: config file #{options.config_path} was not found"
|
||||
exit
|
||||
end
|
||||
|
||||
options.config_path = path
|
||||
end
|
||||
|
||||
opts.on('-l', '--logfile PATH' 'Log file path') { |path| options.log_path = path }
|
||||
opts.on('--loglevel LEVEL', [:fatal, :error, :warn, :info, :debug], 'Logging level (fatal, error, warn, info, debug)') { |level| option.log_level = level }
|
||||
|
||||
opts.on_tail('-h', '--help', 'Show this message') do
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
|
||||
opts.on_tail('-v', '--version', 'Show version') do
|
||||
puts Naily::VERSION
|
||||
exit
|
||||
end
|
||||
end.parse!
|
||||
|
||||
config_path = options.config_path || '/etc/naily/nailyd.conf'
|
||||
|
||||
config = Naily::Config.default
|
||||
config.update(Naily::Config.load(config_path)) if File.exists?(config_path)
|
||||
|
||||
if options.daemonize
|
||||
# After daemonize we can't log to STDOUT, pick a default log file
|
||||
options.log_path ||= "#{Dir.pwd}/naily.log"
|
||||
|
||||
require 'daemons'
|
||||
Daemons.daemonize
|
||||
end
|
||||
|
||||
logger = options.log_path ? Logger.new(options.log_path) : Logger.new(STDOUT)
|
||||
logger.level = Logger.const_get(options.log_level.upcase)
|
||||
|
||||
server = Naily::Server.new(config)
|
||||
server.logger = logger
|
||||
|
||||
server.run
|
||||
|
55
naily/lib/naily/config.rb
Normal file
55
naily/lib/naily/config.rb
Normal file
@ -0,0 +1,55 @@
|
||||
module Naily
|
||||
class ConfigError < StandardError; end
|
||||
class UnknownOptionError < ConfigError
|
||||
attr_reader :name
|
||||
|
||||
def initialize(name)
|
||||
super("Unknown config option #{name}")
|
||||
@name = name
|
||||
end
|
||||
end
|
||||
|
||||
class ParseError < ConfigError
|
||||
attr_reader :line
|
||||
|
||||
def initialize(message, line)
|
||||
super(message)
|
||||
@line = line
|
||||
end
|
||||
end
|
||||
|
||||
class Config
|
||||
def self.load(path)
|
||||
config = new
|
||||
File.open(path) do |f|
|
||||
f.each_line do |line|
|
||||
line.gsub!(/#.*$/, '').trim! # remove comments
|
||||
next if line == ''
|
||||
|
||||
unless /(\S+)\s*=\s*(\S.*)/ ~= line
|
||||
raise ConfigParseErorr.new("Syntax error in line #{line}", line)
|
||||
end
|
||||
|
||||
name, value = $1, $2
|
||||
|
||||
# TODO: validate config option
|
||||
# raise UnknownOptionError.new(name) unless config.respond_to?(:"#{name}=")
|
||||
|
||||
config[name.to_sym] = value
|
||||
end
|
||||
end
|
||||
config
|
||||
end
|
||||
|
||||
def self.default
|
||||
config = {}
|
||||
config[:host] = 'localhost'
|
||||
config[:port] = 5672
|
||||
config[:username] = 'guest'
|
||||
config[:password] = 'guest'
|
||||
config[:queue] = 'naily'
|
||||
config
|
||||
end
|
||||
end
|
||||
end
|
||||
|
8
naily/lib/naily/dispatcher.rb
Normal file
8
naily/lib/naily/dispatcher.rb
Normal file
@ -0,0 +1,8 @@
|
||||
module Naily
|
||||
class Dispatcher
|
||||
def echo(*args)
|
||||
args
|
||||
end
|
||||
end
|
||||
end
|
||||
|
100
naily/lib/naily/server.rb
Normal file
100
naily/lib/naily/server.rb
Normal file
@ -0,0 +1,100 @@
|
||||
require 'eventmachine'
|
||||
require 'amqp'
|
||||
require 'json'
|
||||
require 'logger'
|
||||
require 'naily/dispatcher'
|
||||
|
||||
module Naily
|
||||
class Server
|
||||
attr_reader :options, :delegate
|
||||
|
||||
def initialize(config)
|
||||
@options = options.dup
|
||||
# TODO: validate options
|
||||
@config = config
|
||||
@options.freeze
|
||||
@delegate = options[:delegate] || Dispatcher.new
|
||||
end
|
||||
|
||||
def run
|
||||
EM.run do
|
||||
AMQP.logging = true
|
||||
@connection = AMQP.connect(connection_options)
|
||||
@channel = AMQP::Channel.new(@connection)
|
||||
@channel.on_error do |ch, error|
|
||||
logger.fatal "Channel error #{error}"
|
||||
|
||||
stop { exit }
|
||||
end
|
||||
|
||||
queue = @channel.queue(options[:queue], :durable => true)
|
||||
|
||||
queue.subscribe do |header, payload|
|
||||
dispatch payload
|
||||
end
|
||||
|
||||
Signal.trap('INT') { stop }
|
||||
Signal.trap('TERM') { stop }
|
||||
|
||||
puts "Server started"
|
||||
end
|
||||
end
|
||||
|
||||
def stop(&block)
|
||||
if @connection
|
||||
@connection.close { EM.stop(&block) }
|
||||
else
|
||||
EM.stop(&block)
|
||||
end
|
||||
end
|
||||
|
||||
def logger
|
||||
@logger ||= ::Logger.new(STDOUT)
|
||||
end
|
||||
|
||||
attr_writer :logger
|
||||
|
||||
private
|
||||
|
||||
def dispatch(payload)
|
||||
logger.debug "Got message with payload #{payload.inspect}"
|
||||
|
||||
begin
|
||||
data = JSON.load(payload)
|
||||
rescue
|
||||
logger.error "Error deserializing payload: #{$!}"
|
||||
return
|
||||
end
|
||||
|
||||
unless delegate.respond_to?(data['method'])
|
||||
logger.error "Unsupported RPC call #{data['method']}"
|
||||
return
|
||||
end
|
||||
|
||||
logger.info "Processing RPC call #{data['method']}"
|
||||
|
||||
begin
|
||||
result = delegate.send(data['method'], *data['args'])
|
||||
rescue
|
||||
logger.error "Error running RPC method #{data['method']}: #{$!}"
|
||||
# TODO: send error response in case of RPC call
|
||||
return
|
||||
end
|
||||
|
||||
if data['msg_id']
|
||||
logger.info "Sending RPC call result #{result.inspect} for rpc call #{data['method']}"
|
||||
@channel.default_exchange.publish(JSON.dump(result), :routing_key => data['msg_id'])
|
||||
end
|
||||
end
|
||||
|
||||
def connection_options
|
||||
{
|
||||
:host => options[:host],
|
||||
:port => options[:port],
|
||||
:username => options[:username],
|
||||
:password => options[:password],
|
||||
}.reject { |k, v| v.nil? }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
3
naily/lib/naily/version.rb
Normal file
3
naily/lib/naily/version.rb
Normal file
@ -0,0 +1,3 @@
|
||||
module Naily
|
||||
VERSION = '0.1'
|
||||
end
|
22
naily/naily.gemspec
Normal file
22
naily/naily.gemspec
Normal file
@ -0,0 +1,22 @@
|
||||
$:.unshift File.expand_path('lib', File.dirname(__FILE__))
|
||||
|
||||
require 'naily/version'
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'naily'
|
||||
s.version = Naily::VERSION
|
||||
|
||||
s.summary = 'Backend server for Nailgun'
|
||||
s.description = 'Nailgun deployment job server'
|
||||
s.authors = ['Maxim Kulkin']
|
||||
s.email = ['mkulkin@mirantis.com']
|
||||
|
||||
s.add_dependency 'daemons'
|
||||
s.add_dependency 'amqp'
|
||||
s.add_dependency 'mcollective-client'
|
||||
|
||||
s.files = Dir.glob("{bin,lib}/**/*")
|
||||
s.executables = ['nailyd']
|
||||
s.require_path = 'lib'
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user