* VRFS-2159 merge

This commit is contained in:
Seth Call 2014-09-12 22:30:51 -05:00
parent 770d0acd42
commit 2bd189b697
39 changed files with 1234 additions and 126 deletions

View File

@ -0,0 +1,104 @@
ActiveAdmin.register JamRuby::Connection, :as => 'Connection' do
menu :parent => 'Operations'
actions :index, :show
action_item :only => [:index] do
link_to('Reload All Clients', reload_all_admin_connections_path, class: 'confirm')
end
collection_action :reload_all, :method => :get do
Notification.send_reload(MessageFactory::ALL_NATIVE_CLIENTS)
redirect_to({:action => :index}, {:notice => "All Clients Reloaded!"})
end
action_item :only => [:index] do
link_to('Restart All Clients', restart_all_admin_connections_path, class: 'confirm')
end
collection_action :restart_all, :method => :get do
Notification.send_restart_application(MessageFactory::ALL_NATIVE_CLIENTS)
redirect_to({:action => :index}, {:notice => "All Clients Restarted!"})
end
action_item :only => [:index] do
link_to('Shutdown All Clients', stop_all_admin_connections_path, class: 'confirm')
end
collection_action :stop_all, :method => :get do
Notification.send_stop_application(MessageFactory::ALL_NATIVE_CLIENTS)
redirect_to({:action => :index}, {:notice => "All Clients Shutdown!"})
end
action_item :only => [:show] do
link_to('Reload', reload_admin_connection_path(resource.id), class: 'confirm')
end
member_action :reload, :method => :get do
connection = Connection.find(params[:id])
Notification.send_reload(connection.client_id)
redirect_to({:action => :show}, {:notice => "Reloaded!"})
end
action_item :only => [:show] do
link_to('Restart Client', restart_admin_connection_path(resource.id), class: 'confirm') if resource.client_type == 'client'
end
member_action :restart, :method => :get do
connection = Connection.find(params[:id])
Notification.send_restart_application(connection.client_id)
redirect_to({:action => :show}, {:notice => "Restarted!"})
end
action_item :only => [:show] do
link_to('Shutdown Client', stop_admin_connection_path(resource.id), class: 'confirm') if resource.client_type == 'client'
end
member_action :stop, :method => :get do
connection = Connection.find(params[:id])
Notification.send_stop_application(connection.client_id)
redirect_to({:action => :show}, {:notice => "Shutdown!"})
end
index do
default_actions
column :user_id do |c|
c.user ? c.user.name : ''
end
column :ip_address
column :client_type
column :music_session_id do |c|
c.music_session ? c.music_session.name : ''
end
column :client_id
column :locidispid
column :aasm_state
column :udp_reachable
column :scoring_failures
column :scoring_timeout_occurrences
column :scoring_failures_offset
column :scoring_timeout do |c|
Time.now > c.scoring_timeout ? '' : "#{((c.scoring_timeout - Time.now) / 60).round} minutes left"
end
end
show do
attributes_table do
row :user_id do |c|
c.user ? c.user.name : ''
end
row :ip_address
row :client_type
row :music_session_id do |c|
c.music_session ? c.music_session.name : ''
end
row :client_id
row :locidispid
row :aasm_state
row :udp_reachable
row :scoring_failures
row :scoring_timeout_occurrences
row :scoring_failures_offset
row :scoring_timeout do |c|
Time.now > c.scoring_timeout ? '' : "#{((c.scoring_timeout - Time.now) / 60).round} minutes left"
end
end
end
end

View File

@ -1,4 +1,4 @@
ActiveAdmin.register JamRuby::CrashDump, :as => 'Crash Dump' do
ActiveAdmin.register JamRuby::CrashDump, :as => 'Crash Dump' do
# Note: a lame thing is it's not obvious how to make it search on email instead of user_id.
filter :timestamp
filter :user_email, :as => :string

View File

@ -3,8 +3,6 @@ ActiveAdmin.register_page "Download CSV" do
page_action :create_csv, :method => :post do
puts params.inspect
start_time = params[:score_exports][:start]
end_time = params[:score_exports][:end]

View File

@ -0,0 +1,15 @@
ActiveAdmin.register_page "Current Scoring Load" do
menu :parent => 'Score'
content :title => "Current Scoring Load" do
table_for GetWork.summary do
column "Work", :work_count
column "Who", Proc.new { |connection| "#{connection.first_name} #{connection.last_name} - #{connection.email}" }
column "Errors", Proc.new { |connection| "#{connection.udp_reachable != 'f' ? "" : "No STUN"} #{connection.in_timeout != 'f' ? "in timeout," : ""} #{connection.in_session != 'f' ? "in session" : ""}" }
column "Total Timeouts", :scoring_timeout_occurrences
column "Current Scoring Failures", :scoring_failures
column "Offset", :scoring_failures_offset
end
end
end

View File

@ -19,5 +19,10 @@
context.JK.logger = context.console;
$(function() {
$('a.confirm').click(function() {
return confirm('ARE YOU SURE?!');
})
})
})(window, jQuery);

View File

@ -36,6 +36,7 @@ FactoryGirl.define do
addr 0
locidispid 0
client_type 'client'
scoring_timeout Time.now
sequence(:channel_id) { |n| "Channel#{n}"}
association :user, factory: :user
end

View File

@ -206,4 +206,5 @@ sms_index_single_session.sql
fix_current_scores_user_association.sql
undirected_scores.sql
discard_scores.sql
new_genres.sql
new_genres.sql
get_work_faster.sql

115
db/up/get_work_faster.sql Normal file
View File

@ -0,0 +1,115 @@
-- try to make get_work faster by avoiding temporary tables
-- flag set by client if this connection is able to score.
ALTER TABLE connections ADD COLUMN udp_reachable BOOLEAN DEFAULT TRUE NOT NULL;
-- flag set by the user indicating this connection can't score until this time has elapsed
ALTER TABLE connections ADD COLUMN scoring_timeout TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
-- counter of how many scoring failures have occurred on this connection. once it hits a certain number, we'll set scoring_timeout
ALTER TABLE connections ADD COLUMN scoring_failures INTEGER NOT NULL DEFAULT 0;
-- counter how many times a connection has been put in scoring timeout
ALTER TABLE connections ADD COLUMN scoring_timeout_occurrences INTEGER NOT NULL DEFAULT 0;
-- counter to provide a relative counter offset to avoid writes when scoring_timeout period has elapsed
ALTER TABLE connections ADD COLUMN scoring_failures_offset INTEGER NOT NULL DEFAULT 0;
--DROP FUNCTION IF EXISTS get_work (mylocidispid BIGINT, myaddr BIGINT);
CREATE FUNCTION get_work (my_client_id VARCHAR(64), mylocidispid BIGINT, myaddr BIGINT, return_rows INT, stale_score INTERVAL) RETURNS TABLE (client_id VARCHAR(64)) VOLATILE AS $$
BEGIN
RETURN QUERY WITH
scorable_locations AS (
SELECT DISTINCT locidispid FROM connections WHERE client_type = 'client' AND connections.client_id != my_client_id AND addr != myaddr AND udp_reachable AND NOW() > scoring_timeout AND connections.music_session_id IS NULL AND
locidispid NOT IN (SELECT DISTINCT blocidispid FROM most_recent_scores WHERE alocidispid = mylocidispid AND (current_timestamp - score_dt) < stale_score) AND
locidispid/1000000 IN (SELECT locid FROM geoiplocations WHERE geog && st_buffer((SELECT geog FROM geoiplocations WHERE locid = mylocidispid/1000000), 4023360))
)
SELECT tmp.client_id FROM (SELECT connections.client_id, random() AS r, row_number() OVER (PARTITION BY connections.locidispid) AS rownum FROM connections, scorable_locations
WHERE connections.locidispid = scorable_locations.locidispid AND client_type = 'client' AND connections.client_id != my_client_id AND addr != myaddr AND udp_reachable AND NOW() > scoring_timeout AND connections.music_session_id IS NULL ) tmp WHERE rownum <= 1 ORDER BY r LIMIT return_rows;
RETURN;
END;
$$ LANGUAGE plpgsql;
-- DROP FUNCTION get_work (my_client_id VARCHAR(64), mylocidispid BIGINT, myaddr BIGINT, return_rows INT, stale_score INTERVAL)
-- when a conneciton is involved with a failed score, increment their scoring_failures column, and possible put them in the 'doghouse'
-- DROP FUNCTION connection_failed_score(a_client_id VARCHAR(64), b_client_id VARCHAR(64), timeout_time INTERVAL, failed_score_threshold INT);
CREATE FUNCTION connection_failed_score(a_client_id VARCHAR(64), b_client_id VARCHAR(64), timeout_time INTERVAL, failed_score_threshold INT) RETURNS BOOLEAN VOLATILE AS $$
DECLARE
ret BOOLEAN;
BEGIN
UPDATE connections SET scoring_timeout_occurrences = CASE WHEN scoring_failures = scoring_failures_offset + failed_score_threshold - 1 THEN scoring_timeout_occurrences + 1 ELSE scoring_timeout_occurrences END,
scoring_timeout = CASE WHEN scoring_failures = scoring_failures_offset + failed_score_threshold - 1 THEN CURRENT_TIMESTAMP + timeout_time ELSE scoring_timeout END,
scoring_failures_offset = CASE WHEN scoring_failures = scoring_failures_offset + failed_score_threshold - 1 THEN scoring_failures + 1 WHEN scoring_timeout < CURRENT_TIMESTAMP THEN scoring_failures_offset ELSE scoring_failures_offset + 1 END,
scoring_failures = scoring_failures + 1
WHERE connections.client_id = b_client_id;
UPDATE connections SET scoring_timeout_occurrences = CASE WHEN scoring_failures = scoring_failures_offset + failed_score_threshold - 1 THEN scoring_timeout_occurrences + 1 ELSE scoring_timeout_occurrences END,
scoring_timeout = CASE WHEN scoring_failures = scoring_failures_offset + failed_score_threshold - 1 THEN CURRENT_TIMESTAMP + timeout_time ELSE scoring_timeout END,
scoring_failures_offset = CASE WHEN scoring_failures = scoring_failures_offset + failed_score_threshold - 1 THEN scoring_failures + 1 WHEN scoring_timeout < CURRENT_TIMESTAMP THEN scoring_failures_offset ELSE scoring_failures_offset + 1 END,
scoring_failures = scoring_failures + 1
WHERE connections.client_id = a_client_id RETURNING scoring_timeout > NOW() AS in_timeout INTO ret;
RETURN ret;
END;
$$ LANGUAGE plpgsql;
-- when a connection is involved with a failed score, increment their scoring_failures column, and possible put them in the 'doghouse'
-- DROP FUNCTION connection_good_score(a_client_id VARCHAR(64), b_client_id VARCHAR(64));
CREATE FUNCTION connection_good_score(a_client_id VARCHAR(64), b_client_id VARCHAR(64)) RETURNS BOOLEAN VOLATILE AS $$
DECLARE
ret BOOLEAN;
BEGIN
UPDATE connections SET scoring_failures = CASE WHEN scoring_timeout < CURRENT_TIMESTAMP THEN 0 ELSE scoring_failures END,
scoring_failures_offset = CASE WHEN scoring_timeout < CURRENT_TIMESTAMP THEN 0 ELSE scoring_failures_offset END
WHERE connections.client_id = b_client_id;
UPDATE connections SET scoring_failures = CASE WHEN scoring_timeout < CURRENT_TIMESTAMP THEN 0 ELSE scoring_failures END,
scoring_failures_offset = CASE WHEN scoring_timeout < CURRENT_TIMESTAMP THEN 0 ELSE scoring_failures_offset END
WHERE connections.client_id = a_client_id RETURNING scoring_timeout > NOW() AS in_timeout INTO ret;
RETURN ret;
END;
$$ LANGUAGE plpgsql;
DROP FUNCTION IF EXISTS scorable_locations(my_client_id VARCHAR(64), mylocidispid BIGINT, myaddr BIGINT, stale_score INTERVAL);
CREATE FUNCTION scorable_locations(my_client_id VARCHAR(64), mylocidispid BIGINT, myaddr BIGINT, stale_score INTERVAL) RETURNS TABLE (locidispid BIGINT, addr BIGINT, client_id VARCHAR(64)) VOLATILE AS $$
BEGIN
RETURN QUERY
SELECT c.locidispid, c.addr, c.client_id FROM connections c WHERE client_type = 'client' AND c.udp_reachable AND NOW() > c.scoring_timeout AND c.music_session_id IS NULL AND c.addr != myaddr AND c.client_id != my_client_id AND
c.locidispid NOT IN (SELECT DISTINCT blocidispid FROM most_recent_scores WHERE alocidispid = mylocidispid AND (current_timestamp - score_dt) < stale_score) AND
c.locidispid/1000000 IN (SELECT locid FROM geoiplocations WHERE geog && st_buffer((SELECT geog FROM geoiplocations WHERE locid = mylocidispid/1000000), 4023360));
RETURN;
END;
$$ LANGUAGE plpgsql;
--DROP FUNCTION IF EXISTS get_work_summary (stale_score INTERVAL);
CREATE FUNCTION get_work_summary (stale_score INTERVAL) RETURNS TABLE (work_count BIGINT, client_id VARCHAR(64), email VARCHAR, first_name VARCHAR, last_name VARCHAR, user_id VARCHAR(64), udp_reachable BOOLEAN, in_timeout BOOLEAN, in_session BOOLEAN, scoring_failures INT, scoring_failures_offset INT, scoring_timeout_occurrences INT) VOLATILE AS $$
BEGIN
RETURN QUERY
SELECT SUM(CASE WHEN tmp.test_client_id IS NULL OR tmp.in_session OR tmp.in_timeout OR tmp.udp_reachable = FALSE THEN 0 ELSE 1 END) AS work_count, tmp.client_id AS client_id, users.email, users.first_name, users.last_name, users.id AS user_id, tmp.udp_reachable, tmp.in_timeout, tmp.in_session, tmp.scoring_failures, tmp.scoring_failures_offset, tmp.scoring_timeout_occurrences FROM
(SELECT connections.client_type, scorable_locations.client_id AS test_client_id, connections.client_id AS client_id, connections.user_id AS user_id, connections.udp_reachable, connections.scoring_timeout > NOW() as in_timeout, connections.music_session_id IS NOT NULL AS in_session, connections.scoring_failures, connections.scoring_failures_offset, connections.scoring_timeout_occurrences, scorable_locations.client_id IS NULL AS same_client, row_number() OVER (PARTITION BY connections.locidispid) AS rownum FROM connections LEFT OUTER JOIN scorable_locations(connections.client_id, connections.locidispid, connections.addr, stale_score)
ON connections.locidispid != scorable_locations.locidispid) tmp INNER JOIN users ON tmp.user_id = users.id WHERE tmp.client_type = 'client' GROUP BY tmp.client_id, users.email, users.first_name, users.last_name, users.id, tmp.same_client, tmp.udp_reachable, tmp.in_timeout, tmp.in_session, tmp.scoring_failures, tmp.scoring_failures_offset, tmp.scoring_timeout_occurrences ORDER BY work_count DESC;
RETURN;
END;
$$ LANGUAGE plpgsql;
--DROP FUNCTION IF EXISTS get_work_summary_no_agg(stale_score INTERVAL);
-- useful for debugging get_work_summary
CREATE FUNCTION get_work_summary_no_agg (stale_score INTERVAL) RETURNS TABLE (client_id VARCHAR(64), test_client_id VARCHAR(64), email VARCHAR, first_name VARCHAR, last_name VARCHAR, user_id VARCHAR(64), udp_reachable BOOLEAN, in_timeout BOOLEAN, scoring_failures INT, scoring_failures_offset INT, scoring_timeout_occurrences INT) VOLATILE AS $$
BEGIN
RETURN QUERY
SELECT tmp.client_id AS client_id, tmp.test_client_id, users.email, users.first_name, users.last_name, users.id AS user_id, tmp.udp_reachable, tmp.in_timeout, tmp.scoring_failures, tmp.scoring_failures_offset, tmp.scoring_timeout_occurrences FROM
(SELECT scorable_locations.client_id AS test_client_id, connections.client_id AS client_id, connections.user_id AS user_id, connections.udp_reachable, connections.scoring_timeout > NOW() as in_timeout, connections.scoring_failures, connections.scoring_failures_offset, connections.scoring_timeout_occurrences, scorable_locations.client_id IS NULL AS same_client, row_number() OVER (PARTITION BY connections.locidispid) AS rownum FROM connections LEFT OUTER JOIN scorable_locations(connections.client_id, connections.locidispid, connections.addr, stale_score)
ON connections.locidispid != scorable_locations.locidispid AND connections.client_type = 'client') tmp INNER JOIN users ON tmp.user_id = users.id ORDER BY tmp.client_id;
RETURN;
END;
$$ LANGUAGE plpgsql;

View File

@ -83,6 +83,9 @@ message ClientMessage {
// operational messages
CLIENT_UPDATE = 400;
GENERIC_MESSAGE = 401;
RELOAD = 402;
RESTART_APPLICATION = 403;
STOP_APPLICATION = 404;
SERVER_BAD_STATE_RECOVERED = 900;
@ -179,6 +182,9 @@ message ClientMessage {
// Server-to-All Client operational messages
optional ClientUpdate client_update = 400;
optional GenericMessage generic_message = 401;
optional Reload reload = 402;
optional RestartApplication restart_application = 403;
optional StopApplication stop_application = 404;
// Server-to-Client special messages
optional ServerBadStateRecovered server_bad_state_recovered = 900;
@ -628,6 +634,23 @@ message GenericMessage {
optional string message = 2;
}
// target: client
// this is meant to be a way to poke clients to reload frontend
message Reload {
}
// target: client
// this is meant to be a way to poke clients to restart themselves
message RestartApplication {
}
// target: client
// this is meant to be a way to poke clients to stop themselves
message StopApplication {
}
// route_to: client
// this should follow a ServerBadStateError in the case that the
// websocket gateway recovers from whatever ailed it

View File

@ -44,7 +44,7 @@ module JamRuby
end
# reclaim the existing connection, if ip_address is not nil then perhaps a new address as well
def reconnect(conn, channel_id, reconnect_music_session_id, ip_address, connection_stale_time, connection_expire_time)
def reconnect(conn, channel_id, reconnect_music_session_id, ip_address, connection_stale_time, connection_expire_time, udp_reachable)
music_session_id = nil
reconnected = false
@ -85,7 +85,7 @@ module JamRuby
end
sql =<<SQL
UPDATE connections SET (channel_id, aasm_state, updated_at, music_session_id, joined_session_at, stale_time, expire_time) = ('#{channel_id}', '#{Connection::CONNECT_STATE.to_s}', NOW(), #{music_session_id_expression}, #{joined_session_at_expression}, #{connection_stale_time}, #{connection_expire_time})
UPDATE connections SET (channel_id, aasm_state, updated_at, music_session_id, joined_session_at, stale_time, expire_time, udp_reachable) = ('#{channel_id}', '#{Connection::CONNECT_STATE.to_s}', NOW(), #{music_session_id_expression}, #{joined_session_at_expression}, #{connection_stale_time}, #{connection_expire_time}, #{udp_reachable})
WHERE
client_id = '#{conn.client_id}'
RETURNING music_session_id
@ -183,7 +183,7 @@ SQL
# this number is used by notification logic elsewhere to know
# 'oh the user joined for the 1st time, so send a friend update', or
# 'don't bother because the user has connected somewhere else already'
def create_connection(user_id, client_id, channel_id, ip_address, client_type, connection_stale_time, connection_expire_time, &blk)
def create_connection(user_id, client_id, channel_id, ip_address, client_type, connection_stale_time, connection_expire_time, udp_reachable, &blk)
# validate client_type
raise "invalid client_type: #{client_type}" if client_type != 'client' && client_type != 'browser'
@ -214,8 +214,8 @@ SQL
lock_connections(conn)
conn.exec("INSERT INTO connections (user_id, client_id, channel_id, ip_address, client_type, addr, locidispid, aasm_state, stale_time, expire_time) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
[user_id, client_id, channel_id, ip_address, client_type, addr, locidispid, Connection::CONNECT_STATE.to_s, connection_stale_time, connection_expire_time]).clear
conn.exec("INSERT INTO connections (user_id, client_id, channel_id, ip_address, client_type, addr, locidispid, aasm_state, stale_time, expire_time, udp_reachable) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
[user_id, client_id, channel_id, ip_address, client_type, addr, locidispid, Connection::CONNECT_STATE.to_s, connection_stale_time, connection_expire_time, udp_reachable]).clear
# we just created a new connection-if this is the first time the user has shown up, we need to send out a message to his friends
conn.exec("SELECT count(user_id) FROM connections WHERE user_id = $1", [user_id]) do |result|

View File

@ -743,6 +743,36 @@ module JamRuby
)
end
def reload(client_id)
reload = Jampb::Reload.new()
Jampb::ClientMessage.new(
:type => ClientMessage::Type::RELOAD,
:route_to => CLIENT_TARGET_PREFIX + client_id,
:reload => reload
)
end
def restart_application(client_id)
restart_application = Jampb::RestartApplication.new()
Jampb::ClientMessage.new(
:type => ClientMessage::Type::RESTART_APPLICATION,
:route_to => CLIENT_TARGET_PREFIX + client_id,
:restart_application => restart_application
)
end
def stop_application(client_id)
stop_application = Jampb::StopApplication.new()
Jampb::ClientMessage.new(
:type => ClientMessage::Type::STOP_APPLICATION,
:route_to => CLIENT_TARGET_PREFIX + client_id,
:stop_application => stop_application
)
end
# create a band invitation message
def band_invitation(receiver_id, invitation_id, band_id, photo_url, msg, notification_id, created_at)
band_invitation = Jampb::BandInvitation.new(

View File

@ -66,6 +66,14 @@ module JamRuby
end
end
def in_session?
!music_session_id.nil?
end
def in_scoring_timeout?
scoring_timeout > Time.now
end
def did_expire
self.destroy
end

View File

@ -3,20 +3,29 @@ module JamRuby
self.table_name = "connections"
def self.get_work(mylocidispid, myaddr)
list = self.get_work_list(mylocidispid, myaddr)
def self.get_work(connection, staleness_hours = 120)
list = self.get_work_list(connection, 1, staleness_hours)
return nil if list.nil?
return nil if list.length == 0
return list[0]
end
def self.get_work_list(mylocidispid, myaddr)
r = GetWork.select(:client_id).find_by_sql("select get_work(#{mylocidispid}, #{myaddr}) as client_id")
def self.get_work_list(connection, rows = 25, staleness_hours = 120)
return [] unless connection.udp_reachable # short-circuit 0 results if udp_reachable
return [] if connection.scoring_timeout > Time.now # short-circuit 0 results if in scoring timeout
return [] if connection.in_session?
r = GetWork.select(:client_id).find_by_sql("select get_work('#{connection.client_id}', #{connection.locidispid}, #{connection.addr}, #{rows}, INTERVAL '#{staleness_hours} hours') as client_id")
#puts("r = #{r}")
a = r.map {|i| i.client_id}
#puts("a = #{a}")
a
#return ["blah1", "blah2", "blah3", "blah4", "blah5"]
end
def self.summary(staleness_hours = 120)
r = GetWork.select([:work_count, :client_id, :email, :first_name, :last_name, :user_id, :udp_reachable, :in_timeout, :in_session, :scoring_failures, :scoring_failures_offset, :scoring_timeout_occurrences]).find_by_sql("select work_count, client_id, email, first_name, last_name, user_id, udp_reachable, in_timeout, in_session, scoring_failures, scoring_failures_offset, scoring_timeout_occurrences FROM get_work_summary(INTERVAL '#{staleness_hours} hours')" )
end
end
end

View File

@ -70,6 +70,7 @@ module JamRuby
connection.expire_time = connection_expire_time
connection.as_musician = false
connection.channel_id = channel_id
connection.scoring_timeout = Time.now
unless connection.save
return connection
end

View File

@ -1124,6 +1124,36 @@ module JamRuby
@@mq_router.publish_to_all_clients(msg)
end
def send_reload(client_id)
msg = @@message_factory.reload(client_id)
if client_id == MessageFactory::ALL_NATIVE_CLIENTS
@@mq_router.publish_to_all_clients(msg)
else
@@mq_router.publish_to_client(client_id, msg)
end
end
def send_restart_application(client_id)
msg = @@message_factory.restart_application(client_id)
if client_id == MessageFactory::ALL_NATIVE_CLIENTS
@@mq_router.publish_to_all_clients(msg)
else
@@mq_router.publish_to_client(client_id, msg)
end
end
def send_stop_application(client_id)
msg = @@message_factory.stop_application(client_id)
if client_id == MessageFactory::ALL_NATIVE_CLIENTS
@@mq_router.publish_to_all_clients(msg)
else
@@mq_router.publish_to_client(client_id, msg)
end
end
def send_text_message(message, sender, receiver)
notification = Notification.new

View File

@ -58,5 +58,82 @@ module JamRuby
def self.create_locidispid(geoiplocation_or_geoipblock, jamisp_or_jamcompany)
compute_locidispid(geoiplocation_or_geoipblock.locid, jamisp_or_jamcompany.coid)
end
#
def self.record(current_user, aclientid, aip_address, bclientid, bip_address, score, score_data, udpReachable)
# parameter checks
return {message: 'aclientid not specified', error: true} if aclientid.nil?
return {message: 'aAddr not specified', error: true} if aip_address.nil?
return {message: 'bclientid not specified', error: true} if bclientid.nil?
return {message: 'bAddr not specified', error: true} if bip_address.nil?
return {message: 'aclientid is same as bclientid', error: true} if aclientid == bclientid
aAddr = JamRuby::JamIsp.ip_to_num(aip_address)
return {message: 'aAddr not valid ip_address', error: true} if aAddr.nil?
bAddr = JamRuby::JamIsp.ip_to_num(bip_address)
return {message: 'bAddr not valid ip_address', error: true} if bAddr.nil?
if aAddr == bAddr
result = Score.connection.execute("SELECT connection_failed_score('#{aclientid}', '#{bclientid}', INTERVAL '#{APP_CONFIG.scoring_timeout_minutes} minutes', #{APP_CONFIG.scoring_timeout_threshold})")
in_timeout = result[0]["connection_failed_score"]
return {message: "aAddr and bAddr are the same (to=#{in_timeout})", error: true }
end
if udpReachable == false # we don't care if it's nil; it has to be FALSE explicitely
result = Score.connection.execute("SELECT connection_failed_score('#{aclientid}', '#{bclientid}', INTERVAL '#{APP_CONFIG.scoring_timeout_minutes} minutes', #{APP_CONFIG.scoring_timeout_threshold})")
in_timeout = result[0]["connection_failed_score"]
return {message: "udpReachable is false (to=#{in_timeout})", error: true }
end
if score.nil? || !score.is_a?(Numeric)
return {message: 'score not specified or not numeric' , error: true}
end
result = Score.connection.execute("SELECT connection_good_score('#{aclientid}', '#{bclientid}')");
in_timeout = result[0]["connection_good_score"]
# check a connection's state
aconn = Connection.where(client_id: aclientid, user_id: current_user.id).first
return {message: "a's session not found", error: true} if aconn.nil?
return {message: "a's session addr does not match aAddr", error: true} if aAddr != aconn.addr
# check b connection's state
bconn = Connection.where(client_id: bclientid).first
return {message: "b's session not found", error: true} if bconn.nil?
return {message: "b's session addr does not match bAddr", error: true} if bAddr != bconn.addr
# check sanity of the score
return {message: 'score < 0 or score > 999', error: true} if score < 0 or score > 999
aloc = JamRuby::GeoIpBlocks.lookup(aAddr)
aisp = JamRuby::JamIsp.lookup(aAddr)
return {message: "a's location or isp not found", error: true} if aisp.nil? or aloc.nil?
alocidispid = aloc.locid*1000000+aisp.coid;
bloc = JamRuby::GeoIpBlocks.lookup(bAddr)
bisp = JamRuby::JamIsp.lookup(bAddr)
return {message: "b's location or isp not found", error: true} if bisp.nil? or bloc.nil?
blocidispid = bloc.locid*1000000+bisp.coid
user_info = {}
if aconn.user_id
user_info[:auserid] = aconn.user_id
else
user_info[:alatencytestid] = aconn.latency_tester.id
end
if bconn.user_id
user_info[:buserid] = bconn.user_id
else
user_info[:blatencytestid] = bconn.latency_tester.id
end
JamRuby::Score.createx(alocidispid, aclientid, aAddr, blocidispid, bclientid, bAddr, score.ceil, nil, score_data, user_info)
return {message: "OK (to=#{in_timeout})", error: false}
end
end
end

View File

@ -143,6 +143,7 @@ FactoryGirl.define do
last_jam_audio_latency { user.last_jam_audio_latency if user }
sequence(:channel_id) { |n| "Channel#{n}"}
association :user, factory: :user
scoring_timeout Time.now
end
factory :invitation, :class => JamRuby::Invitation do

View File

@ -8,6 +8,7 @@ describe ConnectionManager, no_transaction: true do
EXPIRE_TIME = 60
STALE_BUT_NOT_EXPIRED = 50
DEFINITELY_EXPIRED = 70
REACHABLE = true
let(:channel_id) {'1'}
@ -48,8 +49,8 @@ describe ConnectionManager, no_transaction: true do
user.save!
user = nil
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
expect { @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME) }.to raise_error(PG::Error)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
expect { @connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE) }.to raise_error(PG::Error)
end
it "create connection then delete it" do
@ -58,7 +59,7 @@ describe ConnectionManager, no_transaction: true do
#user_id = create_user("test", "user2", "user2@jamkazam.com")
user = FactoryGirl.create(:user)
count = @connman.create_connection(user.id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
count = @connman.create_connection(user.id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
count.should == 1
@ -88,7 +89,7 @@ describe ConnectionManager, no_transaction: true do
#user_id = create_user("test", "user2", "user2@jamkazam.com")
user = FactoryGirl.create(:user)
count = @connman.create_connection(user.id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
count = @connman.create_connection(user.id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
count.should == 1
@ -104,7 +105,7 @@ describe ConnectionManager, no_transaction: true do
cc.addr.should == 0x01010101
cc.locidispid.should == 17192000002
@connman.reconnect(cc, channel_id, nil, "33.1.2.3", STALE_TIME, EXPIRE_TIME)
@connman.reconnect(cc, channel_id, nil, "33.1.2.3", STALE_TIME, EXPIRE_TIME, REACHABLE)
cc = Connection.find_by_client_id!(client_id)
cc.connected?.should be_true
@ -217,7 +218,7 @@ describe ConnectionManager, no_transaction: true do
it "flag stale connection" do
client_id = "client_id8"
user_id = create_user("test", "user8", "user8@jamkazam.com")
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
num = JamRuby::Connection.count(:conditions => ['aasm_state = ?','connected'])
num.should == 1
@ -258,7 +259,7 @@ describe ConnectionManager, no_transaction: true do
it "expires stale connection" do
client_id = "client_id8"
user_id = create_user("test", "user8", "user8@jamkazam.com")
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
conn = Connection.find_by_client_id(client_id)
set_updated_at(conn, Time.now - STALE_BUT_NOT_EXPIRED)
@ -284,7 +285,7 @@ describe ConnectionManager, no_transaction: true do
music_session_id = music_session.id
user = User.find(user_id)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10)
connection.errors.any?.should be_false
@ -320,8 +321,8 @@ describe ConnectionManager, no_transaction: true do
client_id2 = "client_id10.12"
user_id = create_user("test", "user10.11", "user10.11@jamkazam.com", :musician => true)
user_id2 = create_user("test", "user10.12", "user10.12@jamkazam.com", :musician => false)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id2, client_id2, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
@connman.create_connection(user_id2, client_id2, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
music_session = FactoryGirl.create(:active_music_session, user_id: user_id)
music_session_id = music_session.id
@ -340,7 +341,7 @@ describe ConnectionManager, no_transaction: true do
client_id = "client_id10.2"
user_id = create_user("test", "user10.2", "user10.2@jamkazam.com")
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
music_session = FactoryGirl.create(:active_music_session, user_id: user_id)
user = User.find(user_id)
@ -356,8 +357,8 @@ describe ConnectionManager, no_transaction: true do
fan_client_id = "client_id10.4"
musician_id = create_user("test", "user10.3", "user10.3@jamkazam.com")
fan_id = create_user("test", "user10.4", "user10.4@jamkazam.com", :musician => false)
@connman.create_connection(musician_id, musician_client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(fan_id, fan_client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(musician_id, musician_client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
@connman.create_connection(fan_id, fan_client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
music_session = FactoryGirl.create(:active_music_session, :fan_access => false, user_id: musician_id)
music_session_id = music_session.id
@ -381,7 +382,7 @@ describe ConnectionManager, no_transaction: true do
music_session_id = music_session.id
user = User.find(user_id2)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
# specify real user id, but not associated with this session
expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) } .to raise_error(ActiveRecord::RecordNotFound)
end
@ -393,7 +394,7 @@ describe ConnectionManager, no_transaction: true do
user = User.find(user_id)
music_session = ActiveMusicSession.new
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
connection = @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10)
connection.errors.size.should == 1
connection.errors.get(:music_session).should == [ValidationMessages::MUSIC_SESSION_MUST_BE_SPECIFIED]
@ -407,7 +408,7 @@ describe ConnectionManager, no_transaction: true do
music_session_id = music_session.id
user = User.find(user_id2)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
# specify real user id, but not associated with this session
expect { @connman.join_music_session(user, client_id, music_session, true, TRACKS, 10) } .to raise_error(ActiveRecord::RecordNotFound)
end
@ -421,7 +422,7 @@ describe ConnectionManager, no_transaction: true do
user = User.find(user_id)
dummy_music_session = ActiveMusicSession.new
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
expect { @connman.leave_music_session(user, Connection.find_by_client_id(client_id), dummy_music_session) }.to raise_error(JamRuby::StateError)
end
@ -436,7 +437,7 @@ describe ConnectionManager, no_transaction: true do
dummy_music_session = ActiveMusicSession.new
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
@connman.join_music_session(user, client_id, music_session, true, TRACKS, 10)
expect { @connman.leave_music_session(user, Connection.find_by_client_id(client_id), dummy_music_session) }.to raise_error(JamRuby::StateError)
end
@ -449,7 +450,7 @@ describe ConnectionManager, no_transaction: true do
music_session_id = music_session.id
user = User.find(user_id)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
@connman.join_music_session(user, client_id, music_session, true, TRACKS, 10)
assert_session_exists(music_session_id, true)
@ -492,7 +493,7 @@ describe ConnectionManager, no_transaction: true do
user = User.find(user_id)
client_id1 = Faker::Number.number(20)
@connman.create_connection(user_id, client_id1, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME)
@connman.create_connection(user_id, client_id1, channel_id, "1.1.1.1", 'client', STALE_TIME, EXPIRE_TIME, REACHABLE)
music_session1 = FactoryGirl.create(:active_music_session, :user_id => user_id)
connection1 = @connman.join_music_session(user, client_id1, music_session1, true, TRACKS, 10)
connection1.errors.size.should == 0

View File

@ -2,21 +2,538 @@ require 'spec_helper'
describe GetWork do
let(:austin_geo) { austin_geoip }
let(:dallas_geo) { dallas_geoip }
before(:each) do
create_phony_database
end
describe "get_work_list" do
it "selects no score when no other clients" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
GetWork.get_work_list(my_connection).should == []
end
it "selects unscored location" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
GetWork.get_work_list(my_connection).should == [other_connection.client_id]
GetWork.get_work_list(other_connection).should == [my_connection.client_id]
end
it "skips scored location" do
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
GetWork.get_work_list(my_connection).should == []
GetWork.get_work_list(other_connection).should == []
end
it "selects scored location with old scores" do
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
# age the scores
Score.connection.execute("UPDATE scores SET score_dt = score_dt - INTERVAL '200 hours'")
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
GetWork.get_work_list(my_connection).should == [other_connection.client_id]
GetWork.get_work_list(other_connection).should == [my_connection.client_id]
end
it "skips scored location with old and new scores" do
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
# age the scores
Score.connection.execute("UPDATE scores SET score_dt = score_dt - INTERVAL '200 hours'")
# create some newer ones
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
GetWork.get_work_list(my_connection).should == []
GetWork.get_work_list(other_connection).should == []
end
it "skips scores regardess of scoring direction" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
score_location(dallas_geo[:locidispid], austin_geo[:locidispid], 20)
GetWork.get_work_list(my_connection).should == []
GetWork.get_work_list(other_connection).should == []
Score.connection.execute('DELETE from scores').check
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
GetWork.get_work_list(my_connection).should == []
GetWork.get_work_list(other_connection).should == []
end
it "selects client even if client has scores to self" do
# this test just verifies that a bit of data in the db doesn't trip up the query
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
score_location(dallas_geo[:locidispid], dallas_geo[:locidispid], 20)
GetWork.get_work_list(my_connection).should == [other_connection.client_id]
GetWork.get_work_list(other_connection).should == [my_connection.client_id]
end
it "selects only one client from a given remote location" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 3)
list = GetWork.get_work_list(my_connection)
(list == [other_connection1.client_id] || list == [other_connection2.client_id]).should be_true # we don't know which one it'll pick
GetWork.get_work_list(other_connection1).should =~ [my_connection.client_id, other_connection2.client_id]
GetWork.get_work_list(other_connection2).should =~ [my_connection.client_id, other_connection1.client_id]
end
it "selects no clients when multiple clients in same location have a score" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 3)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
GetWork.get_work_list(my_connection).should == []
GetWork.get_work_list(other_connection1).should == [other_connection2.client_id]
GetWork.get_work_list(other_connection2).should == [other_connection1.client_id]
end
it "selects two clients from differing, unscored locations" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
GetWork.get_work_list(my_connection).should =~ [other_connection1.client_id, other_connection2.client_id]
GetWork.get_work_list(other_connection1).should =~ [my_connection.client_id, other_connection2.client_id]
GetWork.get_work_list(other_connection2).should =~ [my_connection.client_id, other_connection1.client_id]
end
it "ignores client with the same addr" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 1)
GetWork.get_work_list(my_connection).should == []
end
it "ignores client with the same addr" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 1)
GetWork.get_work_list(my_connection).should == []
end
it "randomizes ordering of selected locations" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
initial_ordering = GetWork.get_work_list(my_connection)
initial_ordering.should =~ [other_connection1.client_id, other_connection2.client_id]
swapped = false
100.times do
# it's randomized results, so we have to let probability win out here. eventually, though (within 100 times? surely), we should see the ordering of work switch up
swapped = (GetWork.get_work_list(my_connection) == [initial_ordering[1], initial_ordering[0]])
break if swapped
end
swapped.should be_true
end
it "excludes udp unreachable clients" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, udp_reachable: false)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
GetWork.get_work_list(my_connection).should == [other_connection2.client_id]
GetWork.get_work_list(other_connection).should == []
GetWork.get_work_list(other_connection2).should == [my_connection.client_id]
end
it "excludes scoring_timeout clients (1)" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, scoring_timeout: 1.days.from_now)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
GetWork.get_work_list(my_connection).should == [other_connection2.client_id]
GetWork.get_work_list(other_connection).should == []
GetWork.get_work_list(other_connection2).should == [my_connection.client_id]
end
it "excludes scoring_timeout clients (2)" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, scoring_timeout: 1.days.from_now)
other_connection2 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 3)
GetWork.get_work_list(my_connection).should == [other_connection2.client_id]
GetWork.get_work_list(other_connection).should == []
GetWork.get_work_list(other_connection2).should == [my_connection.client_id]
end
it "excludes connections in a session" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2,)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
music_session = FactoryGirl.create(:active_music_session, creator: my_connection.user)
other_connection.music_session = music_session
other_connection.save!
GetWork.get_work_list(my_connection).should == [other_connection2.client_id]
GetWork.get_work_list(other_connection).should == []
GetWork.get_work_list(other_connection2).should == [my_connection.client_id]
end
end
describe "record" do
let(:connection1) { FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: austin_ip_as_num, ip_address: austin_ip) }
let(:connection2) { FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: dallas_ip_as_num, ip_address: dallas_ip) }
it "records client errors if no score" do
original_timeout1 = connection1.scoring_timeout
original_timeout2 = connection2.scoring_timeout
result = Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
result.should == {message: 'udpReachable is false (to=f)', error: true}
connection1.reload
connection2.reload
connection1.scoring_failures.should == 1
connection1.scoring_timeout.should == original_timeout1
connection1.scoring_timeout_occurrences.should == 0
connection2.scoring_failures.should == 1
connection2.scoring_timeout.should == original_timeout2
connection1.scoring_timeout_occurrences.should == 0
end
it "records client errors if addr == addr" do
original_timeout1 = connection1.scoring_timeout
original_timeout2 = connection2.scoring_timeout
result = Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection1.ip_address, nil, '', false)
result.should == {message: 'aAddr and bAddr are the same (to=f)', error: true}
connection1.reload
connection2.reload
connection1.scoring_failures.should == 1
connection1.scoring_timeout.should == original_timeout1
connection1.scoring_timeout_occurrences.should == 0
connection2.scoring_failures.should == 1
connection2.scoring_timeout.should == original_timeout2
connection1.scoring_timeout_occurrences.should == 0
end
it "records success if valid" do
original_timeout1 = connection1.scoring_timeout
original_timeout2 = connection2.scoring_timeout
result = Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, 20, '', true)
result.should == {message: 'OK (to=f)', error: false}
connection1.reload
connection2.reload
connection1.scoring_failures.should == 0
connection1.scoring_timeout.should == original_timeout1
connection1.scoring_timeout_occurrences.should == 0
connection2.scoring_failures.should == 0
connection2.scoring_timeout.should == original_timeout2
connection1.scoring_timeout_occurrences.should == 0
end
it "puts in doghouse after enough scoring errors" do
last_result = nil
APP_CONFIG.scoring_timeout_threshold.times do
last_result = Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
end
last_result.should == {message: 'udpReachable is false (to=t)', error: true}
connection1.reload
connection2.reload
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
connection1.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold
expect(connection1.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection1.scoring_timeout_occurrences.should == 1
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
connection2.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold
expect(connection2.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection2.scoring_timeout_occurrences.should == 1
end
describe "while in the doghouse" do
before(:each) do
APP_CONFIG.scoring_timeout_threshold.times do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
end
end
it "another bad score comes in" do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
connection1.reload
connection2.reload
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold + 1
expect(connection1.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection1.scoring_timeout_occurrences.should == 1
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold + 1
expect(connection2.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection2.scoring_timeout_occurrences.should == 1
end
it "a good score comes in" do
# this has no effect when in the dog house
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, 20, '', true)
connection1.reload
connection2.reload
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
expect(connection1.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection1.scoring_timeout_occurrences.should == 1
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
expect(connection2.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection2.scoring_timeout_occurrences.should == 1
end
end
describe "after doghouse expires" do
before(:each) do
APP_CONFIG.scoring_timeout_threshold.times do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
end
# bring scoring_timeout to the past
connection1.scoring_timeout = (APP_CONFIG.scoring_timeout_minutes * 2).minutes.ago
connection1.save!
connection2.scoring_timeout = (APP_CONFIG.scoring_timeout_minutes * 2).minutes.ago
connection2.save!
end
it "another bad score comes in" do
original_timeout1 = connection1.scoring_timeout
original_timeout2 = connection2.scoring_timeout
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
connection1.reload
connection2.reload
# failures shold keep increment, but the user should not yet in_scoring_timeout? because it's only one failure
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold + 1
connection1.in_scoring_timeout?.should be_false
expect(connection1.scoring_timeout).to be_within(1.second).of(original_timeout1)
connection1.scoring_timeout_occurrences.should == 1
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold + 1
connection2.in_scoring_timeout?.should be_false
expect(connection2.scoring_timeout).to be_within(1.second).of(original_timeout2)
connection2.scoring_timeout_occurrences.should == 1
end
it "a good score comes in" do
original_timeout1 = connection1.scoring_timeout
original_timeout2 = connection2.scoring_timeout
# this has no effect when in the dog house
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, 20, '', true)
connection1.reload
connection2.reload
connection1.scoring_failures.should == 0
connection1.in_scoring_timeout?.should be_false
expect(connection1.scoring_timeout).to be_within(1.second).of(original_timeout1)
connection1.scoring_timeout_occurrences.should == 1
connection2.scoring_failures.should == 0
connection2.in_scoring_timeout?.should be_false
expect(connection2.scoring_timeout).to be_within(1.second).of(original_timeout2)
connection2.scoring_timeout_occurrences.should == 1
end
it "a good score comes in, then enough bad scores to be put back into timeout" do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, 20, '', true)
# if a good score comes in while in the dog house, everything should be set back to 0, and bad counting resumes
APP_CONFIG.scoring_timeout_threshold.times do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
end
connection1.reload
connection2.reload
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
connection1.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold
connection1.in_scoring_timeout?.should be_true
expect(connection1.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection1.scoring_timeout_occurrences.should == 2
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
connection2.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold
connection2.in_scoring_timeout?.should be_true
expect(connection2.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection2.scoring_timeout_occurrences.should == 2
end
it "enough bad scores come in to put back into timeout" do
APP_CONFIG.scoring_timeout_threshold.times do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
end
connection1.reload
connection2.reload
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold * 2 # because this user keeps failing with no good scores
connection1.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold * 2 # because this user keeps failing with no good scores
connection1.in_scoring_timeout?.should be_true
expect(connection1.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection1.scoring_timeout_occurrences.should == 2
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold * 2 # because this user keeps failing with no good scores
connection2.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold * 2 # because this user keeps failing with no good scores
connection2.in_scoring_timeout?.should be_true
expect(connection2.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection2.scoring_timeout_occurrences.should == 2
end
end
end
it "get_work_1" do
x = GetWork.get_work(1, 0)
#puts x.inspect
x.should be_nil
describe "summary" do
it "selects no score when no other clients" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
summary = GetWork.summary
summary.length.should == 1
summary[0].work_count.should == '0'
summary[0].client_id.should == my_connection.client_id
summary[0].email.should == my_connection.user.email
summary[0].user_id.should == my_connection.user.id
end
it "selects no score when no other clients" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1, udp_reachable:true)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
summary = GetWork.summary
summary.length.should == 1
summary[0].work_count.should == '0'
summary[0].client_id.should == my_connection.client_id
summary[0].email.should == my_connection.user.email
summary[0].user_id.should == my_connection.user.id
end
it "selects unscored location" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
#score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
summary = GetWork.summary
summary.length.should == 2
summary[0].work_count.should == '1'
summary[1].work_count.should == '1'
end
it "does not count scored location" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
summary = GetWork.summary
summary.length.should == 2
summary[0].work_count.should == '0'
summary[1].work_count.should == '0'
end
it "does not count duplicate location" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
summary = GetWork.summary
summary.length.should == 3
summary[0].work_count.should == '0'
summary[1].work_count.should == '0'
summary[2].work_count.should == '0'
end
it "does not count udp_reachable" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, udp_reachable: false)
summary = GetWork.summary
summary.length.should == 2
summary[0].work_count.should == '0'
summary[1].work_count.should == '0'
end
it "does not count udp_reachable with 2 other clients" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, udp_reachable: false)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
summary = GetWork.summary
summary.length.should == 3
summary[0].work_count.should == '1'
summary[1].work_count.should == '1'
summary[2].work_count.should == '0'
end
it "counts with 3" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
#Database.dump('select * FROM get_work_summary_no_agg(INTERVAL \'120 hours\')');
summary = GetWork.summary
summary.length.should == 3
summary[0].work_count.should == '2'
summary[1].work_count.should == '2'
summary[2].work_count.should == '2'
end
end
it "get_work_list_1" do
x = GetWork.get_work_list(1, 0)
#puts x.inspect
x.should eql([])
end
# todo this needs many more tests!
end

View File

@ -392,8 +392,6 @@ describe Score do
Score.connection.execute("SELECT * FROM scores WHERE score = 22 AND scorer = 0").ntuples.should == 5
Score.connection.execute("SELECT * FROM scores WHERE score = 22 AND scorer = 1").ntuples.should == 5
end
end
end

View File

@ -85,13 +85,13 @@ end
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:deletion, {pre_count: true, reset_ids:false, :except => %w[instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state] })
DatabaseCleaner.clean_with(:deletion, {pre_count: true, reset_ids:false, :except => %w[instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] })
end
config.around(:each) do |example|
# set no_transaction: true as metadata on your test to use deletion strategy instead
if example.metadata[:no_transaction]
DatabaseCleaner.strategy = :deletion, {pre_count: true, reset_ids:false, :except => %w[instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state] }
DatabaseCleaner.strategy = :deletion, {pre_count: true, reset_ids:false, :except => %w[instruments genres icecast_server_groups jamcompany jamisp geoipblocks geoipisp geoiplocations cities regions countries generic_state spatial_ref_sys] }
else
DatabaseCleaner.strategy = :transaction
end

View File

@ -159,14 +159,31 @@ def create_phony_database
GeoIpBlocks.connection.execute("select generate_scores_dataset()").check
end
# helper to create scores for test; most tests don't care about anything but these 3 fields
def score_location(a_locidispid, b_locidispid, latency)
Score.createx(a_locidispid, 'anodeid', 1, b_locidispid, 'bnodeid', 1, latency, nil)
end
def ip_from_num(num)
IPAddr.new(num, Socket::AF_INET).to_s
end
def austin_ip
IPAddr.new(0x0FFFFFFF, Socket::AF_INET).to_s
end
def austin_ip_as_num
0x0FFFFFFF
end
def dallas_ip
IPAddr.new(0x1FFFFFFF, Socket::AF_INET).to_s
end
def dallas_ip_as_num
0x1FFFFFFF
end
# gets related models for an IP in the 1st block from the scores_better_test_data.sql
def austin_geoip
geoiplocation = GeoIpLocations.find_by_locid(17192)
@ -183,6 +200,14 @@ def dallas_geoip
{jamisp: jamisp, geoiplocation: geoiplocation, geoipblock: geoipblock, locidispid: Score.compute_locidispid(geoiplocation.locid, jamisp.coid)}
end
# gets related models for an IP in the 1st block from the scores_better_test_data.sql
def houston_geoip
geoiplocation = GeoIpLocations.find_by_locid(30350)
geoipblock = GeoIpBlocks.find_by_locid(30350)
jamisp = JamIsp.find_by_beginip(geoipblock.beginip)
{jamisp: jamisp, geoiplocation: geoiplocation, geoipblock: geoipblock, locidispid: Score.compute_locidispid(geoiplocation.locid, jamisp.coid)}
end
# attempts to make the creation of a score more straightforward.
# a_geoip and b_geoip are hashes with keys jamisp and geoiplocation (like those created by austin_geoip and dallas_geoip)
def create_score(a_geoip, b_geoip, user_info = {}, a_addr = a_geoip[:jamisp].beginip, b_addr = b_geoip[:jamisp].beginip,

View File

@ -122,6 +122,14 @@ def app_config
100
end
def scoring_timeout_minutes
30
end
def scoring_timeout_threshold
2 # don't put to 1; it'll break tests
end
private
def audiomixer_workspace_path
@ -159,17 +167,21 @@ def set_updated_at(resource, time)
end
def wipe_s3_test_bucket
# don't bother if the user isn't doing AWS tests
if run_tests? :aws
test_config = app_config
s3 = AWS::S3.new(:access_key_id => test_config.aws_access_key_id,
:secret_access_key => test_config.aws_secret_access_key)
test_bucket = s3.buckets[JAMKAZAM_TESTING_BUCKET]
if test_bucket.name == JAMKAZAM_TESTING_BUCKET
test_bucket.objects.each do |obj|
obj.delete
begin
# don't bother if the user isn't doing AWS tests
if run_tests? :aws
test_config = app_config
s3 = AWS::S3.new(:access_key_id => test_config.aws_access_key_id,
:secret_access_key => test_config.aws_secret_access_key)
test_bucket = s3.buckets[JAMKAZAM_TESTING_BUCKET]
if test_bucket.name == JAMKAZAM_TESTING_BUCKET
test_bucket.objects.each do |obj|
obj.delete
end
end
end
rescue
puts "Unable to cleanup AWS"
end
end

View File

@ -73,6 +73,10 @@
PING_ACK : "PING_ACK",
PEER_MESSAGE : "PEER_MESSAGE",
CLIENT_UPDATE : "CLIENT_UPDATE",
GENERIC_MESSAGE : "GENERIC_MESSAGE",
RELOAD : "RELOAD",
RESTART_APPLICATION : "RESTART_APPLICATION",
STOP_APPLICATION : "STOP_APPLICATION",
SERVER_BAD_STATE_RECOVERED: "SERVER_BAD_STATE_RECOVERED",
SERVER_GENERIC_ERROR : "SERVER_GENERIC_ERROR",
SERVER_REJECTION_ERROR : "SERVER_REJECTION_ERROR",

View File

@ -532,7 +532,8 @@
token: $.cookie("remember_token"),
client_type: isClientMode() ? context.JK.clientType() : 'latency_tester',
client_id: isClientMode() ? (gon.global.env == "development" ? $.cookie('client_id') : null): context.jamClient.clientID,
os: context.JK.GetOSAsString()
os: context.JK.GetOSAsString(),
udp_reachable: context.JK.StunInstance ? !context.JK.StunInstance.sync() : null // latency tester doesn't have the stun class loaded
}
var uri = context.gon.websocket_gateway_uri + '?' + $.param(params); // Set in index.html.erb.

View File

@ -5,6 +5,7 @@
//= require fakeJamClientMessages
//= require fakeJamClientRecordings
//= require backend_alerts
//= require stun
(function (context, $) {
@ -13,6 +14,8 @@
context.JK = context.JK || {};
var ALERT_NAMES = context.JK.ALERT_NAMES;
var logger = context.JK.logger;
var stun = null;
$(document).on('JAMKAZAM_CONSTRUCTED', function(e, data) {
@ -21,6 +24,8 @@
// makes window.jamClient / context.jamClient set to something non-null very early on
context.JK.initJamClient(app);
updateScoringIntervals();
})
$(document).on('JAMKAZAM_READY', function() {
@ -34,12 +39,20 @@
checkAudioStopped();
checkMacOSXInstalledCorrectly()
checkMacOSXInstalledCorrectly();
watchPreferencesEvent();
initializeStun(app);
operationalEvents(app);
});
function watchPreferencesEvent() {
context.JK.onBackendEvent(ALERT_NAMES.SHOW_PREFERENCES, 'everywhere', function() {
app.layout.showDialog('client-preferences-dialog')
});
});
}
function checkMacOSXInstalledCorrectly() {
var os = context.jamClient.GetOSAsString();
@ -121,4 +134,40 @@
}
}
function updateScoringIntervals() {
// set scoring intervals
if(context.jamClient.SetScoreWorkTimingInterval){
var success = context.jamClient.SetScoreWorkTimingInterval(
{
interval: gon.global.scoring_get_work_interval,
backoff: gon.global.scoring_get_work_backoff_interval
})
if(!success) logger.warning("unable to set scoring intervals")
}
}
function initializeStun(app) {
stun = new context.JK.Stun(app);
context.JK.StunInstance = stun;
stun.initialize();
}
function operationalEvents(app) {
if(!JK.JamServer || !JK.JamServer.registerMessageCallback) {return;} //no websocket means no events
JK.JamServer.registerMessageCallback(JK.MessageType.RELOAD, function(header, payload) {
window.location.reload();
});
JK.JamServer.registerMessageCallback(JK.MessageType.RESTART_APPLICATION, function(header, payload) {
context.jamClient.RestartApplication();
});
JK.JamServer.registerMessageCallback(JK.MessageType.STOP_APPLICATION, function(header, payload) {
context.jamClient.ShutdownApplication();
});
}
})(window, jQuery);

View File

@ -657,6 +657,13 @@
// Method which sets volume
function UpdateMixer(mixerId) {}
// scoring knobs
function GetScoreWorkTimingInterval() { return {interval: 1000, backoff:60000} }
function SetScoreWorkTimingInterval(knobs) {return true;}
// stun
function NetworkTestResult() { return {remote_udp_blocked: false} }
// Client Update Functions
function IsAppInWritableVolume() { return true; }
function ClientUpdateVersion() { return "Compiled 1.2.3"; }
@ -933,6 +940,10 @@
this.TrackGetChatUsesMusic = TrackGetChatUsesMusic;
this.TrackSetChatUsesMusic = TrackSetChatUsesMusic;
// Scoring Knobs
this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval;
this.SetScoreWorkTimingInterval = SetScoreWorkTimingInterval;
// Client Update
this.IsAppInWritableVolume = IsAppInWritableVolume;
this.ClientUpdateVersion = ClientUpdateVersion;

View File

@ -485,6 +485,19 @@
});
}
function updateUdpReachable(options) {
var id = getId(options);
return $.ajax({
type: "POST",
dataType: "json",
contentType: 'application/json',
url: "/api/users/" + id + "/udp_reachable",
data: JSON.stringify(options),
processData: false
});
}
function updateAvatar(options) {
var id = getId(options);
@ -1206,6 +1219,7 @@
this.getResolvedLocation = getResolvedLocation;
this.getInstruments = getInstruments;
this.getGenres = getGenres;
this.updateUdpReachable = updateUdpReachable;
this.updateAvatar = updateAvatar;
this.deleteAvatar = deleteAvatar;
this.getFilepickerPolicy = getFilepickerPolicy;

View File

@ -0,0 +1,53 @@
(function (context, $) {
"use strict";
context.JK = context.JK || {};
context.JK.Stun = function (app) {
var ALERT_NAMES = context.JK.ALERT_NAMES;
var logger = context.JK.logger;
var udp_blocked = null;
var rest = context.JK.Rest();
function sync(changed) {
if(!context.jamClient.NetworkTestResult) return;
var result = context.jamClient.NetworkTestResult();
if (udp_blocked === null || (result.remote_udp_blocked != udp_blocked)) {
// update the server
if(result.remote_udp_blocked) logger.debug("NO STUN: " + JSON.stringify(result));
else logger.debug("STUN capable: " + JSON.stringify(result));
udp_blocked = result.remote_udp_blocked;
if (changed) changed(result.remote_udp_blocked)
}
return udp_blocked;
}
function watch() {
context.JK.onBackendEvent(ALERT_NAMES.STUN_EVENT, 'everywhere', function () {
logger.debug("handling stun event...");
sync(function (blocked) {
if(app.clientId) {
rest.updateUdpReachable({client_id: app.clientId, udp_reachable: !blocked})
}
});
});
}
function initialize() {
watch();
}
this.initialize = initialize;
this.sync = sync;
}
})(window, jQuery);

View File

@ -12,7 +12,7 @@ class ApiScoringController < ApiController
# if !current_user.id.eql?(conn.user.id) then render :json => {message: 'session not owned by user'}, :status => 403; return end
#puts "ApiScoringController#work(#{clientid}) => locidispid #{c.locidispid}"
result_client_id = JamRuby::GetWork.get_work(conn.locidispid, conn.addr)
result_client_id = JamRuby::GetWork.get_work(conn)
#result_client_id = clientid+'peer'
render :json => {:clientid => result_client_id}, :status => 200
@ -29,7 +29,7 @@ class ApiScoringController < ApiController
# if !current_user.id.eql?(conn.user.id) then render :json => {message: 'session not owned by user'}, :status => 403; return end
result_client_ids = JamRuby::GetWork.get_work_list(conn.locidispid, conn.addr)
result_client_ids = JamRuby::GetWork.get_work_list(conn, APP_CONFIG.getwork_result_size, APP_CONFIG.staleness_hours)
#result_client_ids = [clientid+'peer1', clientid+'peer2']
render :json => {:clientids => result_client_ids}, :status => 200
@ -37,71 +37,27 @@ class ApiScoringController < ApiController
def record # aclientid, aAddr, bclientid, bAddr, score returns nothing
#puts "================= record #{params.inspect}"
aclientid = params[:aclientid]
aip_address = params[:aAddr]
bclientid = params[:bclientid]
bip_address = params[:bAddr]
score = params[:score]
begin
udpReachable = params[:sdetail][:udpReachable]
rescue
udpReachable = nil
end
score_data = params.to_s
if aclientid.nil? then render :json => {message: 'aclientid not specified'}, :status => 400; return end
if aip_address.nil? then render :json => {message: 'aAddr not specified'}, :status => 400; return end
if bclientid.nil? then render :json => {message: 'bclientid not specified'}, :status => 400; return end
if bip_address.nil? then render :json => {message: 'bAddr not specified'}, :status => 400; return end
result = Score.record(current_user, aclientid, aip_address, bclientid, bip_address, score, score_data, udpReachable)
# no score means the test was run but failed, details should still be recorded for later consideration
if score.nil? then render :json => {message: 'score not specified'}, :status => 400; return end
aAddr = JamRuby::JamIsp.ip_to_num(aip_address)
if aAddr.nil? then render :json => {message: 'aAddr not valid ip_address'}, :status => 400; return end
bAddr = JamRuby::JamIsp.ip_to_num(bip_address)
if bAddr.nil? then render :json => {message: 'bAddr not valid ip_address'}, :status => 400; return end
if aAddr == bAddr then render :json => {message: 'aAddr and bAddr are the same'}, :status => 403; return end
if !score.is_a? Numeric then render :json => {message: 'score not valid numeric'}, :status => 400; return end
aconn = Connection.where(client_id: aclientid, user_id: current_user.id).first
if aconn.nil? then render :json => {message: 'a\'s session not found'}, :status => 404; return end
if aAddr != aconn.addr then render :json => {message: 'a\'s session addr does not match aAddr'}, :status => 403; return end
# if !current_user.id.eql?(aconn.user.id) then render :json => {message: 'a\'s session not found'}, :status => 403; return end
bconn = Connection.where(client_id: bclientid).first
if bconn.nil? then render :json => {message: 'b\'s session not found'}, :status => 404; return end
if bAddr != bconn.addr then render :json => {message: 'b\'s session addr does not match bAddr'}, :status => 403; return end
if score < 0 or score > 999 then render :json => {message: 'score < 0 or score > 999'}, :status => 403; return end
aloc = JamRuby::GeoIpBlocks.lookup(aAddr)
aisp = JamRuby::JamIsp.lookup(aAddr)
if aisp.nil? or aloc.nil? then render :json => {message: 'a\'s location or isp not found'}, :status => 404; return end
alocidispid = aloc.locid*1000000+aisp.coid;
bloc = JamRuby::GeoIpBlocks.lookup(bAddr)
bisp = JamRuby::JamIsp.lookup(bAddr)
if bisp.nil? or bloc.nil? then render :json => {message: 'b\'s location or isp not found'}, :status => 404; return end
blocidispid = bloc.locid*1000000+bisp.coid
user_info = {}
if aconn.user_id
user_info[:auserid] = aconn.user_id
if result[:error]
render :json => {message: result[:message]}, :status => 422
else
user_info[:alatencytestid] = aconn.latency_tester.id
render :json => {message: result[:message]}, :status => 200
end
if bconn.user_id
user_info[:buserid] = bconn.user_id
else
user_info[:blatencytestid] = bconn.latency_tester.id
end
JamRuby::Score.createx(alocidispid, aclientid, aAddr, blocidispid, bclientid, bAddr, score.ceil, nil, score_data, user_info)
render :json => {}, :status => 200
end
end

View File

@ -666,6 +666,15 @@ class ApiUsersController < ApiController
end
end
def udp_reachable
Connection.transaction do
@connection = Connection.find_by_client_id(params[:client_id])
@connection.udp_reachable = params[:udp_reachable]
@connection.save
respond_with_model(@connection)
end
end
###################### RECORDINGS #######################
# def recording_index
# @recordings = User.recording_index(current_user, params[:id])

View File

@ -261,5 +261,12 @@ if defined?(Bundler)
config.max_yellow_full_score = 70
config.max_red_full_score = 100
# getWork tweak parameters
config.getwork_result_size = 100 # how many results can we return back in getWork API?
config.staleness_hours = 24 * 5 # how old in hours does a score have to be before we ask for a new one?
config.scoring_timeout_minutes = 30
config.scoring_timeout_threshold = 5
config.scoring_get_work_interval = 1000 # how much time between normal getwork requests
config.scoring_get_work_backoff_interval = 60 * 1000 # how much time between failed getwork requests
end
end

View File

@ -2,4 +2,6 @@ Gon.global.facebook_app_id = Rails.application.config.facebook_app_id
Gon.global.ftue_network_test_packet_size = Rails.application.config.ftue_network_test_packet_size
Gon.global.ftue_network_test_backend_retries = Rails.application.config.ftue_network_test_backend_retries
Gon.global.twitter_public_account = Rails.application.config.twitter_public_account
Gon.global.scoring_get_work_interval = Rails.application.config.scoring_get_work_interval
Gon.global.scoring_get_work_backoff_interval = Rails.application.config.scoring_get_work_backoff_interval
Gon.global.env = Rails.env

View File

@ -295,6 +295,9 @@ SampleApp::Application.routes.draw do
# audio latency
match '/users/:id/audio_latency' => 'api_users#audio_latency', :via => :post
# udp reachable (can stun?)
match '/users/:id/udp_reachable' => 'api_users#udp_reachable', :via => :post
# social
match '/users/:id/share/session/:provider' => 'api_users#share_session', :via => :get
match '/users/:id/share/recording/:provider' => 'api_users#share_recording', :via => :get

View File

@ -238,7 +238,7 @@ describe ApiScoringController do
response.should_not be_success
json = JSON.parse(response.body, :symbolize_names => true)
json.length.should == 1
json[:message].should eql('score not specified')
json[:message].should eql('score not specified or not numeric')
end
it 'record with mary login, bogus, mary_addr, mike, mike_addr, score' do
@ -310,7 +310,16 @@ describe ApiScoringController do
response.should_not be_success
json = JSON.parse(response.body, :symbolize_names => true)
json.length.should == 1
json[:message].should eql('aAddr and bAddr are the same')
json[:message].should eql('aclientid is same as bclientid')
end
it 'record with mary login, mary, mary_addr, mike, mary_addr, score' do
controller.current_user = @mary
post :record, {:format => 'json', :aclientid => @mary_client_id, :aAddr => MARY_IP_ADDRESS, :bclientid => @mike_client_id, :bAddr => MARY_IP_ADDRESS, :score => 20}
response.should_not be_success
json = JSON.parse(response.body, :symbolize_names => true)
json.length.should == 1
json[:message].should eql('aAddr and bAddr are the same (to=f)')
end
it 'record with mary login, mary, mary_addr, mike, mike_addr, -1' do
@ -337,7 +346,7 @@ describe ApiScoringController do
response.should_not be_success
json = JSON.parse(response.body, :symbolize_names => true)
json.length.should == 1
json[:message].should eql('score not valid numeric')
json[:message].should eql('score not specified or not numeric')
end
it 'record with john login, john, john_addr, mike, mike_addr, bogus' do
@ -363,7 +372,7 @@ describe ApiScoringController do
post :record, {:format => 'json', :aclientid => @mary_client_id, :aAddr => MARY_IP_ADDRESS, :bclientid => @mike_client_id, :bAddr => MIKE_IP_ADDRESS, :score => 20}
response.should be_success
json = JSON.parse(response.body, :symbolize_names => true)
json.length.should == 0
json.should == {message: 'OK (to=f)'}
score = Score.findx(MARY_LOCIDISPID, MIKE_LOCIDISPID)
score.should_not be_nil
score.should eq(20)
@ -387,7 +396,7 @@ describe ApiScoringController do
post :record, {:format => 'json', :aclientid => @mary_client_id, :aAddr => MARY_IP_ADDRESS, :bclientid => @mike_client_id, :bAddr => MIKE_IP_ADDRESS, :score => 21.234}
response.should be_success
json = JSON.parse(response.body, :symbolize_names => true)
json.length.should == 0
json.should == {message: 'OK (to=f)'}
score = Score.findx(MARY_LOCIDISPID, MIKE_LOCIDISPID)
score.should_not be_nil
score.should eq(22)

View File

@ -161,6 +161,7 @@ FactoryGirl.define do
addr {JamIsp.ip_to_num(ip_address)}
locidispid 0
client_type 'client'
scoring_timeout Time.now
sequence(:channel_id) { |n| "Channel#{n}"}
end

View File

@ -159,6 +159,11 @@ def create_phony_database
GeoIpBlocks.connection.execute("select generate_scores_dataset()").check
end
# helper to create scores for test; most tests don't care about anything but these 3 fields
def score_location(a_locidispid, b_locidispid, latency)
Score.createx(a_locidispid, 'anodeid', 1, b_locidispid, 'bnodeid', 1, latency, nil)
end
def austin_ip
IPAddr.new(0x0FFFFFFF, Socket::AF_INET).to_s
end

View File

@ -570,8 +570,9 @@ module JamWebsockets
reconnect_music_session_id = options["music_session_id"]
client_type = options["client_type"]
os = options["os"]
udp_reachable = options["udp_reachable"].nil? ? true : options["udp_reachable"] == 'true'
@log.info("handle_login: client_type=#{client_type} token=#{token} client_id=#{client_id} channel_id=#{client.channel_id}")
@log.info("handle_login: client_type=#{client_type} token=#{token} client_id=#{client_id} channel_id=#{client.channel_id} udp_reachable=#{udp_reachable}")
if client_type == Connection::TYPE_LATENCY_TESTER
handle_latency_tester_login(client_id, client_type, client)
@ -638,7 +639,7 @@ module JamWebsockets
recording_id = nil
ConnectionManager.active_record_transaction do |connection_manager|
music_session_id, reconnected = connection_manager.reconnect(connection, client.channel_id, reconnect_music_session_id, remote_ip, connection_stale_time, connection_expire_time)
music_session_id, reconnected = connection_manager.reconnect(connection, client.channel_id, reconnect_music_session_id, remote_ip, connection_stale_time, connection_expire_time, udp_reachable)
if music_session_id.nil?
# if this is a reclaim of a connection, but music_session_id comes back null, then we need to check if this connection was IN a music session before.
@ -674,7 +675,7 @@ module JamWebsockets
unless connection
# log this connection in the database
ConnectionManager.active_record_transaction do |connection_manager|
connection_manager.create_connection(user.id, client.client_id, client.channel_id, remote_ip, client_type, connection_stale_time, connection_expire_time) do |conn, count|
connection_manager.create_connection(user.id, client.client_id, client.channel_id, remote_ip, client_type, connection_stale_time, connection_expire_time, udp_reachable) do |conn, count|
user.update_addr_loc(Connection.find_by_client_id(client.client_id), User::JAM_REASON_LOGIN)
if count == 1
Notification.send_friend_update(user.id, true, conn)
@ -736,7 +737,7 @@ module JamWebsockets
if connection.stale?
ConnectionManager.active_record_transaction do |connection_manager|
heartbeat_interval, connection_stale_time, connection_expire_time = determine_connection_times(context.user, context.client_type)
connection_manager.reconnect(connection, connection.music_session_id, nil, connection_stale_time, connection_expire_time)
connection_manager.reconnect(connection, connection.music_session_id, nil, connection_stale_time, connection_expire_time, udp_reachable)
end
end
end
@ -839,6 +840,7 @@ module JamWebsockets
def access_p2p(client_id, user, msg)
return nil
# ping_request and ping_ack messages are special in that they are simply allowed
if msg.type == ClientMessage::Type::PING_REQUEST || msg.type == ClientMessage::Type::PING_ACK
return nil
@ -864,6 +866,16 @@ module JamWebsockets
# belong to
#access_p2p(to_client_id, context.user, client_msg)
# quick and dirty safegaurds against the most dangerous operational messages from being sent by malicious clients
if client_msg.type == ClientMessage::Type::RELOAD ||
client_msg.type == ClientMessage::Type::CLIENT_UPDATE ||
client_msg.type == ClientMessage::Type::GENERIC_MESSAGE ||
client_msg.type == ClientMessage::Type::RESTART_APPLICATION ||
client_msg.type == ClientMessage::Type::STOP_APPLICATION
@@log.error("malicious activity")
raise SessionError, "not allowed"
end
if to_client_id.nil? || to_client_id == 'undefined' # javascript translates to 'undefined' in many cases
raise SessionError, "empty client_id specified in peer-to-peer message"
end

View File

@ -91,6 +91,7 @@ FactoryGirl.define do
ip_address '1.1.1.1'
as_musician true
client_type 'client'
scoring_timeout Time.now
sequence(:channel_id) { |n| "Channel#{n}"}
end