kamerling 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +50 -0
- data/.ruby-version +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +90 -0
- data/LICENCE +661 -0
- data/README.md +6 -0
- data/Rakefile +18 -0
- data/bin/kamerling +9 -0
- data/config/reek.yml +18 -0
- data/kamerling.gemspec +30 -0
- data/lib/kamerling/addr.rb +18 -0
- data/lib/kamerling/client.rb +2 -0
- data/lib/kamerling/core_extensions/main.rb +17 -0
- data/lib/kamerling/core_extensions.rb +1 -0
- data/lib/kamerling/handler.rb +24 -0
- data/lib/kamerling/http_api.rb +38 -0
- data/lib/kamerling/logging.rb +20 -0
- data/lib/kamerling/message.rb +47 -0
- data/lib/kamerling/migrations/1_basic_schema.rb +45 -0
- data/lib/kamerling/net_dispatcher.rb +8 -0
- data/lib/kamerling/project.rb +2 -0
- data/lib/kamerling/receiver.rb +11 -0
- data/lib/kamerling/registrar.rb +9 -0
- data/lib/kamerling/registration.rb +2 -0
- data/lib/kamerling/repo.rb +32 -0
- data/lib/kamerling/repos.rb +65 -0
- data/lib/kamerling/result.rb +2 -0
- data/lib/kamerling/server/http.rb +26 -0
- data/lib/kamerling/server/sock.rb +32 -0
- data/lib/kamerling/server/tcp.rb +19 -0
- data/lib/kamerling/server/udp.rb +20 -0
- data/lib/kamerling/server_runner.rb +50 -0
- data/lib/kamerling/task.rb +2 -0
- data/lib/kamerling/task_dispatcher.rb +29 -0
- data/lib/kamerling/uuid.rb +17 -0
- data/lib/kamerling/uuid_object.rb +79 -0
- data/lib/kamerling/views/clients.slim +6 -0
- data/lib/kamerling/views/layout.slim +6 -0
- data/lib/kamerling/views/project.slim +11 -0
- data/lib/kamerling/views/projects.slim +9 -0
- data/lib/kamerling/views/root.slim +6 -0
- data/lib/kamerling.rb +29 -0
- data/spec/kamerling/addr_spec.rb +27 -0
- data/spec/kamerling/client_spec.rb +9 -0
- data/spec/kamerling/core_extensions/main_spec.rb +18 -0
- data/spec/kamerling/handler_spec.rb +30 -0
- data/spec/kamerling/http_api_spec.rb +93 -0
- data/spec/kamerling/logging_spec.rb +73 -0
- data/spec/kamerling/message_spec.rb +70 -0
- data/spec/kamerling/net_dispatcher_spec.rb +21 -0
- data/spec/kamerling/receiver_spec.rb +22 -0
- data/spec/kamerling/registrar_spec.rb +16 -0
- data/spec/kamerling/repo_spec.rb +60 -0
- data/spec/kamerling/repos_spec.rb +136 -0
- data/spec/kamerling/server/http_spec.rb +22 -0
- data/spec/kamerling/server/tcp_spec.rb +46 -0
- data/spec/kamerling/server/udp_spec.rb +44 -0
- data/spec/kamerling/server_runner_spec.rb +65 -0
- data/spec/kamerling/task_dispatcher_spec.rb +23 -0
- data/spec/kamerling/task_spec.rb +9 -0
- data/spec/kamerling/uuid_object_spec.rb +101 -0
- data/spec/kamerling/uuid_spec.rb +24 -0
- data/spec/spec_helper.rb +26 -0
- metadata +325 -0
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'reek/rake/task'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
|
5
|
+
task default: %i[spec rubocop reek]
|
6
|
+
|
7
|
+
Rake::TestTask.new :spec do |task|
|
8
|
+
task.test_files = FileList['spec/**/*_spec.rb']
|
9
|
+
task.warning = true
|
10
|
+
end
|
11
|
+
|
12
|
+
Reek::Rake::Task.new do |task|
|
13
|
+
task.config_files = 'config/reek.yml'
|
14
|
+
task.fail_on_error = false
|
15
|
+
task.reek_opts = '--quiet'
|
16
|
+
end
|
17
|
+
|
18
|
+
Rubocop::RakeTask.new
|
data/bin/kamerling
ADDED
data/config/reek.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
DuplicateMethodCall:
|
2
|
+
exclude:
|
3
|
+
- Kamerling#self.class_definition_from
|
4
|
+
- Kamerling::Handler#handle
|
5
|
+
|
6
|
+
IrresponsibleModule:
|
7
|
+
enabled: false
|
8
|
+
|
9
|
+
NestedIterators:
|
10
|
+
max_allowed_nesting: 2
|
11
|
+
|
12
|
+
UncommunicativeMethodName:
|
13
|
+
exclude:
|
14
|
+
- Kamerling#self.UUIDObject
|
15
|
+
|
16
|
+
UnusedParameters:
|
17
|
+
exclude:
|
18
|
+
- Kamerling::Server::HTTP#initialize
|
data/kamerling.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.author = 'Piotr Szotkowski'
|
3
|
+
gem.description = 'A network server for distributing computations across different clients speaking TCP or UDP.'
|
4
|
+
gem.email = 'p.szotkowski@tele.pw.edu.pl'
|
5
|
+
gem.homepage = 'https://github.com/chastell/kamerling'
|
6
|
+
gem.license = 'AGPL-3.0'
|
7
|
+
gem.name = 'kamerling'
|
8
|
+
gem.summary = 'Kamerling: a computation network server'
|
9
|
+
gem.version = '0.0.1'
|
10
|
+
|
11
|
+
gem.files = `git ls-files -z`.split "\0"
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |path| File.basename path }
|
13
|
+
gem.test_files = gem.files.grep %r{^spec/.*\.rb$}
|
14
|
+
|
15
|
+
gem.add_dependency 'after_do', '~> 0.3.0'
|
16
|
+
gem.add_dependency 'sequel', '~> 4.4'
|
17
|
+
gem.add_dependency 'sinatra', '~> 1.4'
|
18
|
+
gem.add_dependency 'slim', '~> 2.0'
|
19
|
+
gem.add_dependency 'sqlite3', '~> 1.3'
|
20
|
+
|
21
|
+
gem.add_development_dependency 'bogus', '~> 0.1.3'
|
22
|
+
gem.add_development_dependency 'minitest', '~> 5.0'
|
23
|
+
gem.add_development_dependency 'minitest-focus', '~> 1.1'
|
24
|
+
gem.add_development_dependency 'nokogiri', '~> 1.6'
|
25
|
+
gem.add_development_dependency 'rack-test', '~> 0.6.2'
|
26
|
+
gem.add_development_dependency 'rake', '~> 10.1'
|
27
|
+
gem.add_development_dependency 'reek', '~> 1.3'
|
28
|
+
gem.add_development_dependency 'rerun', '~> 0.8.2'
|
29
|
+
gem.add_development_dependency 'rubocop', '~> 0.18.0'
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Kamerling
|
2
|
+
Addr = Struct.new :host, :port, :prot do
|
3
|
+
def connectable?
|
4
|
+
TCPSocket.open(*self).close
|
5
|
+
true
|
6
|
+
rescue Errno::ECONNREFUSED
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_a
|
11
|
+
[host, port]
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"#{host}:#{port} (#{prot})"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Kamerling module CoreExtensions module Main
|
2
|
+
module_function
|
3
|
+
|
4
|
+
def req param
|
5
|
+
method = caller.first[/`(.*)'$/, 1]
|
6
|
+
callsite = Class === self ? "#{name}.#{method}" : "#{self.class}##{method}"
|
7
|
+
raise "#{callsite}: param #{param} is required"
|
8
|
+
end
|
9
|
+
|
10
|
+
def warn_off
|
11
|
+
verbose = $VERBOSE
|
12
|
+
$VERBOSE = false
|
13
|
+
yield
|
14
|
+
ensure
|
15
|
+
$VERBOSE = verbose
|
16
|
+
end
|
17
|
+
end end end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'core_extensions/main'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Kamerling class Handler
|
2
|
+
UnknownInput = Class.new RuntimeError
|
3
|
+
|
4
|
+
def initialize receiver: Receiver.new, registrar: Registrar.new
|
5
|
+
@receiver, @registrar = receiver, registrar
|
6
|
+
end
|
7
|
+
|
8
|
+
def handle input, addr
|
9
|
+
message = Message.new input
|
10
|
+
case message.type
|
11
|
+
when :RGST
|
12
|
+
registrar.register addr: addr, client_uuid: message.client_uuid,
|
13
|
+
project_uuid: message.project_uuid
|
14
|
+
when :RSLT
|
15
|
+
receiver.receive addr: addr, client_uuid: message.client_uuid,
|
16
|
+
data: message.payload, task_uuid: message.task_uuid
|
17
|
+
end
|
18
|
+
rescue Message::UnknownType => exception
|
19
|
+
raise UnknownInput, exception.message
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :receiver, :registrar
|
23
|
+
private :receiver, :registrar
|
24
|
+
end end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'slim'
|
4
|
+
|
5
|
+
module Kamerling class HTTPAPI < Sinatra::Base
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
configure { set repos: Repos }
|
9
|
+
|
10
|
+
get '/' do
|
11
|
+
slim :root
|
12
|
+
end
|
13
|
+
|
14
|
+
get '/clients' do
|
15
|
+
warn_off { slim :clients, locals: { clients: repos.clients } }
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/projects' do
|
19
|
+
warn_off { slim :projects, locals: { projects: repos.projects } }
|
20
|
+
end
|
21
|
+
|
22
|
+
get '/projects/:project_uuid' do
|
23
|
+
project = repos.project params['project_uuid']
|
24
|
+
clients = repos.clients_for project
|
25
|
+
tasks = repos.tasks_for project
|
26
|
+
warn_off { slim :project, locals: { clients: clients, tasks: tasks } }
|
27
|
+
end
|
28
|
+
|
29
|
+
post '/projects' do
|
30
|
+
uuid = params.fetch('uuid') { UUID.new }
|
31
|
+
repos << Project.new(name: params['name'], uuid: uuid)
|
32
|
+
redirect '/projects'
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
delegate repos: :settings
|
38
|
+
end end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'after_do'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Kamerling module Logging
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def log_to logger: Logger.new($stdout)
|
8
|
+
Server::Sock.extend AfterDo
|
9
|
+
Server::Sock.before :start do |*, server|
|
10
|
+
logger.info "start #{server.addr}"
|
11
|
+
end
|
12
|
+
Server::Sock.before :handle do |input, client_addr|
|
13
|
+
logger.info "connect #{client_addr}"
|
14
|
+
logger.debug "received #{client_addr} #{input}"
|
15
|
+
end
|
16
|
+
Server::Sock.after :stop do |*, server|
|
17
|
+
logger.info "stop #{server.addr}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Kamerling class Message
|
2
|
+
KnownTypes = %w[DATA PING RGST RSLT]
|
3
|
+
UnknownType = Class.new RuntimeError
|
4
|
+
|
5
|
+
def self.[](client: req(:client), payload: req(:payload),
|
6
|
+
project: req(:project), task: req(:task), type: req(:type))
|
7
|
+
new "#{type}\0\0\0\0\0\0\0\0\0\0\0\0" + UUID.bin(client.uuid) +
|
8
|
+
UUID.bin(project.uuid) + UUID.bin(task.uuid) + payload
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize raw
|
12
|
+
@raw = raw
|
13
|
+
type = raw[0..3]
|
14
|
+
raise UnknownType, type unless KnownTypes.include? type or type.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def == other
|
18
|
+
raw == other.raw
|
19
|
+
end
|
20
|
+
|
21
|
+
def client_uuid
|
22
|
+
UUID[raw[16..31]]
|
23
|
+
end
|
24
|
+
|
25
|
+
def payload
|
26
|
+
raw[64..-1]
|
27
|
+
end
|
28
|
+
|
29
|
+
def project_uuid
|
30
|
+
UUID[raw[32..47]]
|
31
|
+
end
|
32
|
+
|
33
|
+
def task_uuid
|
34
|
+
UUID[raw[48..63]]
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
raw
|
39
|
+
end
|
40
|
+
|
41
|
+
def type
|
42
|
+
raw[0..3].to_sym
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :raw
|
46
|
+
private :raw
|
47
|
+
end end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
create_table :clients do
|
4
|
+
uuid :uuid, primary_key: true
|
5
|
+
boolean :busy, null: false
|
6
|
+
inet :host, null: false
|
7
|
+
integer :port, null: false
|
8
|
+
string :prot, null: false
|
9
|
+
end
|
10
|
+
|
11
|
+
create_table :projects do
|
12
|
+
uuid :uuid, primary_key: true
|
13
|
+
string :name, null: false
|
14
|
+
end
|
15
|
+
|
16
|
+
create_table :registrations do
|
17
|
+
uuid :uuid, primary_key: true
|
18
|
+
inet :host, null: false
|
19
|
+
integer :port, null: false
|
20
|
+
string :prot, null: false
|
21
|
+
foreign_key :client_uuid, :clients, index: true, null: false,
|
22
|
+
type: :uuid
|
23
|
+
foreign_key :project_uuid, :projects, index: true, null: false,
|
24
|
+
type: :uuid
|
25
|
+
end
|
26
|
+
|
27
|
+
create_table :results do
|
28
|
+
uuid :uuid, primary_key: true
|
29
|
+
bytea :data, null: false
|
30
|
+
inet :host, null: false
|
31
|
+
integer :port, null: false
|
32
|
+
string :prot, null: false
|
33
|
+
foreign_key :client_uuid, :clients, index: true, null: false, type: :uuid
|
34
|
+
foreign_key :task_uuid, :tasks, index: true, null: false, type: :uuid
|
35
|
+
end
|
36
|
+
|
37
|
+
create_table :tasks do
|
38
|
+
uuid :uuid, primary_key: true
|
39
|
+
boolean :done, null: false
|
40
|
+
bytea :data, null: false
|
41
|
+
foreign_key :project_uuid, :projects, index: true, null: false,
|
42
|
+
type: :uuid
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Kamerling class Receiver
|
2
|
+
def receive addr: req(:addr), client_uuid: req(:client_uuid),
|
3
|
+
data: req(:data), repos: Repos, task_uuid: req(:task_uuid)
|
4
|
+
client = repos[Client][client_uuid]
|
5
|
+
task = repos[Task][task_uuid]
|
6
|
+
result = Result.new addr: addr, client: client, data: data, task: task
|
7
|
+
client.busy = false
|
8
|
+
task.done = true
|
9
|
+
repos << result << client << task
|
10
|
+
end
|
11
|
+
end end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Kamerling class Registrar
|
2
|
+
def register addr: req(:addr), client_uuid: req(:client_uuid),
|
3
|
+
project_uuid: req(:project_uuid), repos: Repos
|
4
|
+
client = repos[Client][client_uuid]
|
5
|
+
project = repos[Project][project_uuid]
|
6
|
+
reg = Registration.new addr: addr, client: client, project: project
|
7
|
+
repos[Registration] << reg
|
8
|
+
end
|
9
|
+
end end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Kamerling class Repo
|
2
|
+
NotFound = Class.new RuntimeError
|
3
|
+
|
4
|
+
def initialize klass, source
|
5
|
+
@klass, @source = klass, source
|
6
|
+
end
|
7
|
+
|
8
|
+
def << object
|
9
|
+
hash = object.to_h
|
10
|
+
warn_off { source << hash }
|
11
|
+
rescue Sequel::UniqueConstraintViolation
|
12
|
+
warn_off { source.where(uuid: object.uuid).update hash }
|
13
|
+
end
|
14
|
+
|
15
|
+
def [] uuid
|
16
|
+
hash = warn_off { source[uuid: uuid] }
|
17
|
+
raise NotFound, "#{klass} with UUID #{uuid}" unless hash
|
18
|
+
klass.from_h hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def all
|
22
|
+
source.all.map { |hash| klass.from_h hash }
|
23
|
+
end
|
24
|
+
|
25
|
+
def related_to object
|
26
|
+
key = "#{object.class.name.split('::').last.downcase}_uuid".to_sym
|
27
|
+
source.where(key => object.uuid).map { |hash| klass.from_h hash }
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :klass, :source
|
31
|
+
private :klass, :source
|
32
|
+
end end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
warn_off { require 'sequel' }
|
2
|
+
|
3
|
+
Sequel.extension :migration
|
4
|
+
|
5
|
+
module Kamerling class Repos
|
6
|
+
class << self
|
7
|
+
attr_writer :repos
|
8
|
+
|
9
|
+
def << object
|
10
|
+
repos[object.class] << object
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def [] klass
|
15
|
+
repos[klass]
|
16
|
+
end
|
17
|
+
|
18
|
+
def clients
|
19
|
+
repos[Client].all
|
20
|
+
end
|
21
|
+
|
22
|
+
def clients_for project
|
23
|
+
repos[Registration].related_to(project).map(&:client)
|
24
|
+
end
|
25
|
+
|
26
|
+
def db= db
|
27
|
+
warn_off { Sequel::Migrator.run db, "#{__dir__}/migrations" }
|
28
|
+
@repos = nil
|
29
|
+
@db = db
|
30
|
+
end
|
31
|
+
|
32
|
+
def free_clients_for project
|
33
|
+
clients_for(project).reject(&:busy)
|
34
|
+
end
|
35
|
+
|
36
|
+
def next_task_for project
|
37
|
+
repos[Task].related_to(project).reject(&:done).first
|
38
|
+
end
|
39
|
+
|
40
|
+
def project project_uuid
|
41
|
+
repos[Project][project_uuid]
|
42
|
+
end
|
43
|
+
|
44
|
+
def projects
|
45
|
+
repos[Project].all
|
46
|
+
end
|
47
|
+
|
48
|
+
def tasks_for project
|
49
|
+
repos[Task].related_to project
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def db
|
55
|
+
@db ||= self.db = Sequel.sqlite
|
56
|
+
end
|
57
|
+
|
58
|
+
def repos
|
59
|
+
@repos ||= Hash.new do |repos, klass|
|
60
|
+
table = "#{klass.name.split('::').last.downcase}s".to_sym
|
61
|
+
repos[klass] = Repo.new klass, warn_off { db[table] }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Kamerling module Server class HTTP
|
2
|
+
attr_reader :addr
|
3
|
+
|
4
|
+
def initialize addr: req(:addr)
|
5
|
+
@addr = addr
|
6
|
+
end
|
7
|
+
|
8
|
+
def join
|
9
|
+
thread.join
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
@thread = Thread.new do
|
14
|
+
Rack::Handler::WEBrick.run HTTPAPI, Host: addr.host, Port: addr.port
|
15
|
+
end
|
16
|
+
loop { break if addr.connectable? }
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def stop
|
21
|
+
thread.exit.join
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :thread
|
25
|
+
private :thread
|
26
|
+
end end end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Kamerling module Server class Sock
|
2
|
+
attr_reader :addr
|
3
|
+
|
4
|
+
def initialize addr: req(:addr), handler: Handler.new
|
5
|
+
@addr = addr
|
6
|
+
@handler = handler
|
7
|
+
end
|
8
|
+
|
9
|
+
def join
|
10
|
+
thread.join
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
@thread = Thread.new { run_loop }
|
15
|
+
wait_till_started
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def stop
|
20
|
+
thread.exit.join
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :handler, :thread
|
24
|
+
private :handler, :thread
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def handle input, client_addr
|
29
|
+
handler.handle input, client_addr
|
30
|
+
rescue Handler::UnknownInput
|
31
|
+
end
|
32
|
+
end end end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Kamerling module Server class TCP < Sock
|
2
|
+
private
|
3
|
+
|
4
|
+
def handle_connection socket
|
5
|
+
client_addr = Addr[*socket.remote_address.ip_unpack, :TCP]
|
6
|
+
input = socket.read
|
7
|
+
handle input, client_addr
|
8
|
+
ensure
|
9
|
+
socket.close
|
10
|
+
end
|
11
|
+
|
12
|
+
def run_loop
|
13
|
+
Socket.tcp_server_loop(*addr) { |socket| handle_connection socket }
|
14
|
+
end
|
15
|
+
|
16
|
+
def wait_till_started
|
17
|
+
loop { break if addr.connectable? }
|
18
|
+
end
|
19
|
+
end end end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Kamerling module Server class UDP < Sock
|
2
|
+
private
|
3
|
+
|
4
|
+
def handle_connection socket
|
5
|
+
input, conn = socket.recvfrom 2**16
|
6
|
+
client_addr = Addr[conn[3], conn[1], :UDP]
|
7
|
+
handle input, client_addr
|
8
|
+
end
|
9
|
+
|
10
|
+
def run_loop
|
11
|
+
socket = UDPSocket.new.tap { |server| server.bind(*addr) }
|
12
|
+
loop { handle_connection socket if IO.select [socket] }
|
13
|
+
ensure
|
14
|
+
socket.close if socket
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait_till_started
|
18
|
+
200.times { thread.run }
|
19
|
+
end
|
20
|
+
end end end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Kamerling class ServerRunner
|
4
|
+
Settings = Struct.new(*%i[db host http tcp udp])
|
5
|
+
|
6
|
+
def initialize args, classes: def_classes, orm: Sequel, repos: Repos
|
7
|
+
@args = args
|
8
|
+
repos.db = orm.connect settings.db
|
9
|
+
@servers = { http: :TCP, tcp: :TCP, udp: :UDP }.map do |type, prot|
|
10
|
+
addr = Addr[settings.host, settings.send(type), prot]
|
11
|
+
classes[type].new addr: addr if addr.port
|
12
|
+
end.compact
|
13
|
+
end
|
14
|
+
|
15
|
+
def join
|
16
|
+
servers.each(&:join)
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
servers.each(&:start)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :args, :servers
|
25
|
+
private :args, :servers
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def def_classes
|
30
|
+
{ http: Server::HTTP, tcp: Server::TCP, udp: Server::UDP }
|
31
|
+
end
|
32
|
+
|
33
|
+
def settings
|
34
|
+
@settings ||= Settings.new.tap do |sets|
|
35
|
+
sets.db = 'sqlite::memory:'
|
36
|
+
sets.host = '127.0.0.1'
|
37
|
+
OptionParser.new do |opts|
|
38
|
+
opts.on("--db #{sets.db}", String, 'database') do |db|
|
39
|
+
sets.db = db
|
40
|
+
end
|
41
|
+
opts.on("--host #{sets.host}", String, 'server host') do |host|
|
42
|
+
sets.host = host
|
43
|
+
end
|
44
|
+
opts.on('--http 0', Integer, 'HTTP port') { |http| sets.http = http }
|
45
|
+
opts.on('--tcp 0', Integer, 'TCP port') { |tcp| sets.tcp = tcp }
|
46
|
+
opts.on('--udp 0', Integer, 'UDP port') { |udp| sets.udp = udp }
|
47
|
+
end.parse! args
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Kamerling class TaskDispatcher
|
2
|
+
def initialize net_dispatcher: NetDispatcher.new, repos: Repos
|
3
|
+
@net_dispatcher = net_dispatcher
|
4
|
+
@repos = repos
|
5
|
+
end
|
6
|
+
|
7
|
+
def dispatch
|
8
|
+
repos.projects.each do |project|
|
9
|
+
repos.free_clients_for(project).each do |client|
|
10
|
+
task = repos.next_task_for(project)
|
11
|
+
dispatch_task client: client, project: project, task: task if task
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :net_dispatcher, :repos
|
17
|
+
private :net_dispatcher, :repos
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def dispatch_task client: req(:client), project: req(:project),
|
22
|
+
task: req(:task)
|
23
|
+
message = Message[client: client, payload: task.data, project: project,
|
24
|
+
task: task, type: :DATA]
|
25
|
+
net_dispatcher.dispatch client.addr, message.to_s
|
26
|
+
client.busy = true
|
27
|
+
repos << client
|
28
|
+
end
|
29
|
+
end end
|