Revision b7f7d214
Added by Marc Dequènes over 14 years ago
- ID b7f7d214220c8cb982ed8e5dfdb68bd7a199e1d0
bin/mapmaker | ||
---|---|---|
|
||
class MapMakerInterface
|
||
include CyborgServerInterface
|
||
include CyborgServerDefaultInterface
|
||
include CyborgServerRootInterfaceAddon
|
||
|
||
class DNS < Services::DNS::System
|
||
include CyborgServerInterface
|
||
... | ... | |
"coucou: " + data.inspect
|
||
end
|
||
|
||
class Zones
|
||
class Taiste
|
||
include CyborgServerInterface
|
||
|
||
def api_methods
|
||
["coucou", "toto", "plop"]
|
||
end
|
||
|
||
dynamic_interface {|node_name| ">>> #{node_name} <<<" }
|
||
end
|
||
|
||
class Zones
|
||
include CyborgServerStatefulInterface
|
||
|
||
def initialize
|
||
... | ... | |
@dns.zones
|
||
end
|
||
|
||
def method_missing(method_name, *args)
|
||
session = args.shift
|
||
|
||
zone_name = method_name.to_s
|
||
if api_methods.include?(zone_name)
|
||
resource_key = "dns/zones/#{zone_name}"
|
||
session.store.get(resource_key) { Zone.new(zone_name) }
|
||
else
|
||
super
|
||
end
|
||
end
|
||
stateful_dynamic_interface("dns/zones/#NODE#") {|node_name| Zone.new(node_name) }
|
||
|
||
class Zone < Services::DNS::Zone
|
||
include CyborgServerEmbeddedInterface
|
lib/cyborghood/cyborg/conversation.rb | ||
---|---|---|
#--
|
||
# CyborgHood, a distributed system management software.
|
||
# Copyright (c) 2009-2010 Marc Dequènes (Duck) <Duck@DuckCorp.org>
|
||
#
|
||
# This program is free software: you can redistribute it and/or modify
|
||
# it under the terms of the GNU General Public License as published by
|
||
# the Free Software Foundation, either version 3 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
#++
|
||
|
||
require 'cyborghood/cyborg/session'
|
||
|
||
|
||
module CyborgHood
|
||
class Conversation < EventMachine::Protocols::LineAndTextProtocol
|
||
private_class_method :new
|
||
|
||
# don't rely on EventMachine's default, it may change one day
|
||
MaxLineLength = 16*1024
|
||
|
||
EOD = "\033[0J"
|
||
COMMAND_PATTERN = "^#{CyborgServerInterfaceBase::NODE_PATTERN}(?: ([+?]+))?$"
|
||
|
||
def initialize(interface)
|
||
@interface = interface
|
||
|
||
super
|
||
|
||
@config = Config.instance
|
||
@split_data_mode = false
|
||
@split_data_cmd = nil
|
||
@split_data = []
|
||
@session = Session.new
|
||
end
|
||
|
||
def send_line(msg)
|
||
send_data "#{msg}\n"
|
||
logger.debug "Sent data [#{identifier}]: #{msg}"
|
||
end
|
||
|
||
def post_init
|
||
logger.debug "New conversation with #{identifier}"
|
||
send_line "220 #{PRODUCT} #{VERSION} - #{@config.bot_name}"
|
||
end
|
||
|
||
def receive_line(data)
|
||
|
||
return if data.empty?
|
||
|
||
if data == EOD
|
||
logger.debug "Received EOD [#{identifier}]"
|
||
exit_split_mode
|
||
else
|
||
if @split_data_mode
|
||
logger.debug "Received data (split mode) [#{identifier}]: #{data}"
|
||
|
||
@split_data << data
|
||
else
|
||
logger.debug "Received data [#{identifier}]: #{data}"
|
||
|
||
unless data =~ Regexp.new(COMMAND_PATTERN)
|
||
logger.error "Error [#{identifier}]: syntax error"
|
||
send_line "552 syntax error in command"
|
||
return
|
||
end
|
||
cmd = $1
|
||
flags = $2 || ""
|
||
|
||
if flags.index '?'
|
||
send_line "250+ ok"
|
||
send_line({'exists' => @interface.has_node?(cmd)}.to_yaml)
|
||
return
|
||
end
|
||
|
||
if flags.index '+'
|
||
enter_split_mode(cmd)
|
||
else
|
||
receive_command(cmd)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
def receive_command(cmd, data = nil)
|
||
logger.debug "Executing command '#{cmd}' [#{identifier}]"
|
||
send_line @interface.call(@session, cmd, data)
|
||
end
|
||
|
||
def receive_error(msg)
|
||
logger.error "Error [#{identifier}]: #{msg}"
|
||
end
|
||
|
||
def unbind
|
||
logger.debug "Conversation finished with #{identifier}"
|
||
@session.clear
|
||
end
|
||
|
||
protected
|
||
|
||
def enter_split_mode(cmd)
|
||
if @split_data_mode
|
||
logger.error "Error [#{identifier}]: already in split mode"
|
||
send_line "551 protocol error"
|
||
@split_data_mode = false
|
||
@split_data_cmd = nil
|
||
else
|
||
logger.debug "Entered split mode for command '#{cmd}' [#{identifier}]"
|
||
@split_data_mode = true
|
||
@split_data_cmd = cmd
|
||
end
|
||
@split_data = []
|
||
end
|
||
|
||
def exit_split_mode
|
||
if @split_data_mode
|
||
logger.debug "Quit split mode for command '#{@split_data_cmd}' [#{identifier}]"
|
||
receive_command(@split_data_cmd, @split_data.join("\n"))
|
||
else
|
||
logger.error "Error [#{identifier}]: not in split mode"
|
||
send_line "551 protocol error"
|
||
end
|
||
@split_data_mode = false
|
||
@split_data_cmd = nil
|
||
@split_data = []
|
||
end
|
||
end
|
||
|
||
class ConversationUNIXSocket < Conversation
|
||
public_class_method :new
|
||
|
||
def identifier
|
||
"unix_socket/#{@signature}"
|
||
end
|
||
end
|
||
end
|
lib/cyborghood/cyborg/interface.rb | ||
---|---|---|
|
||
|
||
module CyborgHood
|
||
module CyborgServerInterface
|
||
def self.included(base)
|
||
base.class_eval("include CyborgServerInterfaceBase")
|
||
base.class_eval("include Singleton")
|
||
end
|
||
end
|
||
|
||
module CyborgServerEmbeddedInterface
|
||
def self.included(base)
|
||
base.class_eval("include CyborgServerInterfaceBase")
|
||
base.export_parent_methods
|
||
end
|
||
end
|
||
|
||
module CyborgServerStatefulInterface
|
||
end
|
||
|
||
# the base mixin (not intended to be used directly, but...)
|
||
module CyborgServerInterfaceBase
|
||
NODE_PATTERN = "((?:\/|(?:\/[a-zA-Z0-9._]+)+[?=]?))"
|
||
|
||
... | ... | |
end
|
||
end
|
||
|
||
module CyborgServerDefaultInterface
|
||
# structural mixins
|
||
|
||
module CyborgServerInterface
|
||
def self.included(base)
|
||
base.class_eval("include CyborgServerInterfaceBase")
|
||
base.class_eval("include Singleton")
|
||
base.extend(ClassMethods)
|
||
end
|
||
|
||
module ClassMethods
|
||
def dynamic_interface(&resource_generator)
|
||
class_eval do
|
||
class_inheritable_reader :resource_generator
|
||
|
||
def method_missing(method_name, *args)
|
||
node_name = method_name.to_s
|
||
if api_methods.include?(node_name)
|
||
self.resource_generator.call(node_name)
|
||
else
|
||
super
|
||
end
|
||
end
|
||
end
|
||
|
||
write_inheritable_attribute(:resource_generator, resource_generator)
|
||
end
|
||
end
|
||
end
|
||
|
||
module CyborgServerEmbeddedInterface
|
||
def self.included(base)
|
||
base.class_eval("include CyborgServerInterfaceBase")
|
||
base.export_parent_methods
|
||
end
|
||
end
|
||
|
||
module CyborgServerStatefulInterface
|
||
def self.included(base)
|
||
base.class_eval("include CyborgServerInterfaceBase")
|
||
base.extend(ClassMethods)
|
||
end
|
||
|
||
module ClassMethods
|
||
def stateful_dynamic_interface(resource_key_pattern, &resource_generator)
|
||
class_eval do
|
||
class_inheritable_reader :resource_key_pattern, :resource_generator
|
||
|
||
def method_missing(method_name, *args)
|
||
session = args.shift
|
||
|
||
node_name = method_name.to_s
|
||
if api_methods.include?(node_name)
|
||
resource_key = self.resource_key_pattern.gsub("#NODE#", node_name)
|
||
session.store.get(resource_key) { self.resource_generator.call(node_name) }
|
||
else
|
||
super
|
||
end
|
||
end
|
||
end
|
||
|
||
write_inheritable_attribute(:resource_key_pattern, resource_key_pattern)
|
||
write_inheritable_attribute(:resource_generator, resource_generator)
|
||
end
|
||
end
|
||
end
|
||
|
||
# additional mixin
|
||
|
||
module CyborgServerRootInterfaceAddon
|
||
PROTOCOL_VERSION = "0.1~"
|
||
|
||
def self.included(base)
|
lib/cyborghood/cyborg/server.rb | ||
---|---|---|
|
||
require 'eventmachine'
|
||
require 'cyborghood/cyborg/interface'
|
||
require 'shellwords'
|
||
require 'cyborghood/cyborg/conversation'
|
||
|
||
|
||
module CyborgHood
|
||
... | ... | |
end
|
||
end
|
||
|
||
class Conversation < EventMachine::Protocols::LineAndTextProtocol
|
||
private_class_method :new
|
||
|
||
# don't rely on EventMachine's default, it may change one day
|
||
MaxLineLength = 16*1024
|
||
|
||
EOD = "\033[0J"
|
||
COMMAND_PATTERN = "^#{CyborgServerInterfaceBase::NODE_PATTERN}(?: ([+?]+))?$"
|
||
|
||
def initialize(interface)
|
||
@interface = interface
|
||
|
||
super
|
||
|
||
@config = Config.instance
|
||
@split_data_mode = false
|
||
@split_data_cmd = nil
|
||
@split_data = []
|
||
@session = Session.new
|
||
end
|
||
|
||
def send_line(msg)
|
||
send_data "#{msg}\n"
|
||
logger.debug "Sent data [#{identifier}]: #{msg}"
|
||
end
|
||
|
||
def post_init
|
||
logger.debug "New conversation with #{identifier}"
|
||
send_line "220 #{PRODUCT} #{VERSION} - #{@config.bot_name}"
|
||
end
|
||
|
||
def receive_line(data)
|
||
|
||
return if data.empty?
|
||
|
||
if data == EOD
|
||
logger.debug "Received EOD [#{identifier}]"
|
||
exit_split_mode
|
||
else
|
||
if @split_data_mode
|
||
logger.debug "Received data (split mode) [#{identifier}]: #{data}"
|
||
|
||
@split_data << data
|
||
else
|
||
logger.debug "Received data [#{identifier}]: #{data}"
|
||
|
||
unless data =~ Regexp.new(COMMAND_PATTERN)
|
||
logger.error "Error [#{identifier}]: syntax error"
|
||
send_line "552 syntax error in command"
|
||
return
|
||
end
|
||
cmd = $1
|
||
flags = $2 || ""
|
||
|
||
if flags.index '?'
|
||
send_line "250+ ok"
|
||
send_line({'exists' => @interface.has_node?(cmd)}.to_yaml)
|
||
return
|
||
end
|
||
|
||
if flags.index '+'
|
||
enter_split_mode(cmd)
|
||
else
|
||
receive_command(cmd)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
def receive_command(cmd, data = nil)
|
||
logger.debug "Executing command '#{cmd}' [#{identifier}]"
|
||
send_line @interface.call(@session, cmd, data)
|
||
end
|
||
|
||
def receive_error(msg)
|
||
logger.error "Error [#{identifier}]: #{msg}"
|
||
end
|
||
|
||
def unbind
|
||
logger.debug "Conversation finished with #{identifier}"
|
||
@session.clear
|
||
end
|
||
|
||
protected
|
||
|
||
def enter_split_mode(cmd)
|
||
if @split_data_mode
|
||
logger.error "Error [#{identifier}]: already in split mode"
|
||
send_line "551 protocol error"
|
||
@split_data_mode = false
|
||
@split_data_cmd = nil
|
||
else
|
||
logger.debug "Entered split mode for command '#{cmd}' [#{identifier}]"
|
||
@split_data_mode = true
|
||
@split_data_cmd = cmd
|
||
end
|
||
@split_data = []
|
||
end
|
||
|
||
def exit_split_mode
|
||
if @split_data_mode
|
||
logger.debug "Quit split mode for command '#{@split_data_cmd}' [#{identifier}]"
|
||
receive_command(@split_data_cmd, @split_data.join("\n"))
|
||
else
|
||
logger.error "Error [#{identifier}]: not in split mode"
|
||
send_line "551 protocol error"
|
||
end
|
||
@split_data_mode = false
|
||
@split_data_cmd = nil
|
||
@split_data = []
|
||
end
|
||
end
|
||
|
||
class ConversationUNIXSocket < Conversation
|
||
public_class_method :new
|
||
|
||
def identifier
|
||
"unix_socket/#{@signature}"
|
||
end
|
||
end
|
||
|
||
# default interface if not overridden
|
||
class EmptyInterface
|
||
include CyborgServerInterface
|
||
include CyborgServerDefaultInterface
|
||
include CyborgServerRootInterfaceAddon
|
||
end
|
||
|
||
module SimpleServer
|
||
... | ... | |
EmptyInterface.instance
|
||
end
|
||
end
|
||
|
||
class Session
|
||
attr_reader :store
|
||
|
||
def initialize
|
||
@store = Store.new
|
||
end
|
||
|
||
def clear
|
||
@store.values.each do |obj|
|
||
obj.__destroy if obj.respond_to? :__destroy
|
||
end
|
||
@store.clear
|
||
end
|
||
|
||
class Store < Hash
|
||
def get(key)
|
||
obj = self[key]
|
||
if obj.nil? and block_given?
|
||
obj = yield
|
||
self[key] = obj
|
||
end
|
||
obj
|
||
end
|
||
end
|
||
end
|
||
end
|
lib/cyborghood/cyborg/session.rb | ||
---|---|---|
#--
|
||
# CyborgHood, a distributed system management software.
|
||
# Copyright (c) 2009-2010 Marc Dequènes (Duck) <Duck@DuckCorp.org>
|
||
#
|
||
# This program is free software: you can redistribute it and/or modify
|
||
# it under the terms of the GNU General Public License as published by
|
||
# the Free Software Foundation, either version 3 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
#++
|
||
|
||
|
||
module CyborgHood
|
||
class Session
|
||
attr_reader :store
|
||
|
||
def initialize
|
||
@store = Store.new
|
||
end
|
||
|
||
def clear
|
||
@store.values.each do |obj|
|
||
obj.__destroy if obj.respond_to? :__destroy
|
||
end
|
||
@store.clear
|
||
end
|
||
|
||
class Store < Hash
|
||
def get(key)
|
||
obj = self[key]
|
||
if obj.nil? and block_given?
|
||
obj = yield
|
||
self[key] = obj
|
||
end
|
||
obj
|
||
end
|
||
end
|
||
end
|
||
end
|
Also available in: Unified diff
[evol] MapMaker / Cyborg server interface: cleanup, reorg, ...