kamerling 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|