* fixing tests, and adding a bunch more jam-admin page work for managing icecast, as well as the beginnings of the REST API for icecast (VRFS-1002 and VRFS-1006)

This commit is contained in:
Seth Call 2014-01-19 02:20:44 +00:00
parent f327317ede
commit e57946afd2
56 changed files with 1276 additions and 377 deletions

View File

@ -9,7 +9,6 @@ ActiveAdmin.register_page "Bootstrap" do
server = IcecastServer.new
server.template = template
server.hostname = hostname
server.location = hostname
server.server_id = hostname
server.save!
@ -19,62 +18,69 @@ ActiveAdmin.register_page "Bootstrap" do
page_action :brew_template, :method => :post do
# to make this template, I just did 'brew install icecast', and then based the rest of this code on what I saw in /usr/local/etc/icecast.xml
limit = IcecastLimit.new
limit.clients = 100
limit.sources = 2
limit.queue_size = 524288
limit.client_timeout = 30
limit.header_timeout = 15
limit.source_timeout = 10
limit.burst_size = 65535
limit.save!
admin_auth = IcecastAdminAuthentication.new
admin_auth.source_password = 'blueberryjam'
admin_auth.relay_user = 'jamjam'
admin_auth.relay_password = 'blueberryjam'
admin_auth.admin_user = 'jamjam'
admin_auth.admin_password = 'blueberryjam'
admin_auth.save!
IcecastServer.transaction do
path = IcecastPath.new
path.base_dir = '/usr/local/Cellar/icecast/2.3.3/share/icecast'
path.log_dir = '/usr/local/Cellar/icecast/2.3.3/var/log/icecast'
path.web_root = '/usr/local/Cellar/icecast/2.3.3/share/icecast/web'
path.admin_root = '/usr/local/Cellar/icecast/2.3.3/share/icecast/admin'
path.pid_file = nil
path.save!
security = IcecastSecurity.new
security.chroot = false
security.save!
limit = IcecastLimit.new
limit.clients = 100
limit.sources = 2
limit.queue_size = 524288
limit.client_timeout = 30
limit.header_timeout = 15
limit.source_timeout = 10
limit.burst_size = 65535
limit.save!
logging = IcecastLogging.new
logging.access_log = 'access.log'
logging.error_log = 'error.log'
logging.log_level = 3 # you might want to change this after creating the template
logging.log_size = 10000
logging.save!
admin_auth = IcecastAdminAuthentication.new
admin_auth.source_pass = 'blueberryjam'
admin_auth.relay_user = 'jamjam'
admin_auth.relay_pass = 'blueberryjam'
admin_auth.admin_user = 'jamjam'
admin_auth.admin_pass = 'blueberryjam'
admin_auth.save!
listen_socket1 = IcecastListenSocket.new
listen_socket1.port = 8000
listen_socket1.save!
path = IcecastPath.new
path.base_dir = '/usr/local/Cellar/icecast/2.3.3/share/icecast'
path.log_dir = '/usr/local/Cellar/icecast/2.3.3/var/log/icecast'
path.web_root = '/usr/local/Cellar/icecast/2.3.3/share/icecast/web'
path.admin_root = '/usr/local/Cellar/icecast/2.3.3/share/icecast/admin'
path.pid_file = nil
path.save!
listen_socket2 = IcecastListenSocket.new
listen_socket2.port = 8001
listen_socket2.save!
security = IcecastSecurity.new
security.chroot = false
security.save!
logging = IcecastLogging.new
logging.access_log = 'access.log'
logging.error_log = 'error.log'
logging.log_level = 3 # you might want to change this after creating the template
logging.log_size = 10000
logging.save!
listen_socket1 = IcecastListenSocket.new
listen_socket1.port = 8000
listen_socket1.save!
listen_socket2 = IcecastListenSocket.new
listen_socket2.port = 8001
listen_socket2.save!
template = IcecastTemplate.new
template.name = "Brew-#{IcecastTemplate.count + 1}"
template.location = '@work'
template.admin_email = 'nobody@jamkazam.com'
template.fileserve = true
template.limit = limit
template.admin_auth = admin_auth
template.path = path
template.security = security
template.logging = logging
template.listen_sockets = [listen_socket1, listen_socket2]
template.save!
end
template = IcecastTemplate.new
template.name = "Brew-#{IcecastTemplate.count + 1}"
template.admin_email = 'nobody@jamkazam.com'
template.fileserve = true
template.limit = limit
template.admin_auth = admin_auth
template.path = path
template.security = security
template.logging = logging
template.listen_sockets = [listen_socket1, listen_socket2]
template.save!
redirect_to admin_bootstrap_path, :notice => "Brew template created. Create a server now with that template specified."
end

View File

@ -0,0 +1,4 @@
ActiveAdmin.register JamRuby::IcecastServerMount, :as => 'ServerMounts' do
menu :parent => 'Icecast'
end

View File

@ -0,0 +1,3 @@
ActiveAdmin.register JamRuby::IcecastServerRelay, :as => 'ServerRelays' do
menu :parent => 'Icecast'
end

View File

@ -0,0 +1,3 @@
ActiveAdmin.register JamRuby::IcecastServerSocket, :as => 'ServerListenSockets' do
menu :parent => 'Icecast'
end

View File

@ -0,0 +1,3 @@
ActiveAdmin.register JamRuby::IcecastTemplateSocket, :as => 'TemplateListenSockets' do
menu :parent => 'Icecast'
end

View File

@ -6,6 +6,11 @@ class Footer < ActiveAdmin::Component
end
end
module ActiveAdmin
class BaseController
with_role :admin
end
end
ActiveAdmin.setup do |config|

View File

@ -19,7 +19,7 @@ rm -rf $TARGET
mkdir -p $PG_BUILD_OUT
mkdir -p $PG_RUBY_PACKAGE_OUT
#bundle update
bundle update
echo "building migrations"
bundle exec pg_migrate build --source . --out $PG_BUILD_OUT --test --verbose

View File

@ -42,16 +42,16 @@ CREATE TABLE icecast_admin_authentications (
-- The DEFAULT username for all source connections is 'source' but
-- this option allows to specify a DEFAULT password. This and the username
-- can be changed in the individual mount sections.
source_password VARCHAR(64) NOT NULL,
source_pass VARCHAR(64) NOT NULL,
-- Used in the master server as part of the authentication when a slave requests
-- the list of streams to relay. The DEFAULT username is 'relay'
relay_user VARCHAR(64) NOT NULL,
relay_password VARCHAR(64) NOT NULL,
relay_pass VARCHAR(64) NOT NULL,
--The username/password used for all administration functions.
admin_user VARCHAR(64) NOT NULL,
admin_password VARCHAR(64) NOT NULL,
admin_pass VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
@ -63,7 +63,7 @@ CREATE TABLE icecast_admin_authentications (
CREATE TABLE icecast_directories (
id VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
yp_url_timeout INTEGER NOT NULL DEFAULT 15,
yp_url VARCHAR(1024) NOT NULL UNIQUE,
yp_url VARCHAR(1024) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
@ -98,127 +98,131 @@ CREATE TABLE icecast_relays (
-- eg /different.ogg
local_mount VARCHAR(1024),
-- eg joe. could be null
username VARCHAR(64),
relay_username VARCHAR(64),
-- user password
password VARCHAR(64),
relay_shoutcast_metadata BOOLEAN DEFAULT FALSE,
relay_pass VARCHAR(64),
relay_shoutcast_metadata INTEGER DEFAULT 0,
--- relay only if we have someone wanting to listen
on_demand BOOLEAN DEFAULT TRUE,
on_demand INTEGER DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE icecast_user_authentications(
id VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
--"htpasswd or url"
-- real name is type
authentication_type VARCHAR(16) DEFAULT 'url',
-- these are for httpasswd
filename VARCHAR(1024),
allow_duplicate_users BOOLEAN,
CREATE UNLOGGED TABLE icecast_user_authentications(
id VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
--"htpasswd or url"
-- real name is type
authentication_type VARCHAR(16) DEFAULT 'url',
-- these are for httpasswd
filename VARCHAR(1024),
allow_duplicate_users INTEGER,
-- these options are for url
-- eg value="http://myauthserver.com/stream_start.php"
mount_add VARCHAR(1024),
--value="http://myauthserver.com/stream_end.php"
mount_remove VARCHAR(1024),
--value="http://myauthserver.com/listener_joined.php"
listener_add VARCHAR(1024),
--value="http://myauthserver.com/listener_left.php"
listener_remove VARCHAR(1024),
-- value="user"
username VARCHAR(64),
-- value="pass"
password VARCHAR(64),
-- value="icecast-auth-user: 1"
auth_header VARCHAR(64) DEFAULT 'icecast-auth-user: 1',
-- value="icecast-auth-timelimit:"
timelimit_header VARCHAR(64) DEFAULT 'icecast-auth-timelimit:',
-- these options are for url
-- eg value="http://myauthserver.com/stream_start.php"
mount_add VARCHAR(1024),
--value="http://myauthserver.com/stream_end.php"
mount_remove VARCHAR(1024),
--value="http://myauthserver.com/listener_joined.php"
listener_add VARCHAR(1024),
--value="http://myauthserver.com/listener_left.php"
listener_remove VARCHAR(1024),
-- value="user"
unused_username VARCHAR(64),
-- value="pass"
unused_pass VARCHAR(64),
-- value="icecast-auth-user: 1"
auth_header VARCHAR(64) DEFAULT 'icecast-auth-user: 1',
-- value="icecast-auth-timelimit:"
timelimit_header VARCHAR(64) DEFAULT 'icecast-auth-timelimit:',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNLOGGED TABLE icecast_mounts (
id VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
-- eg/example-complex.ogg
mount_name VARCHAR(1024) UNIQUE NOT NULL,
username VARCHAR(64),
password VARCHAR(64),
max_listeners INTEGER DEFAULT 4,
max_listener_duration INTEGER DEFAULT 3600,
-- dump of the stream coming through on this mountpoint.
-- eg /tmp/dump-example1.ogg
dump_file VARCHAR(1024),
-- intro music to play
-- This optional value specifies a mountpoint that clients are automatically moved to
-- if the source shuts down or is not streaming at the time a listener connects.
intro VARCHAR(1024),
fallback_mount VARCHAR(1024),
-- When enabled, this allows a connecting source client or relay on this mountpoint
-- to move listening clients back from the fallback mount.
fallback_override BOOLEAN DEFAULT TRUE,
id VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
-- eg/example-complex.ogg
name VARCHAR(1024) UNIQUE NOT NULL,
source_username VARCHAR(64),
source_pass VARCHAR(64),
max_listeners INTEGER DEFAULT 4,
max_listener_duration INTEGER DEFAULT 3600,
-- dump of the stream coming through on this mountpoint.
-- eg /tmp/dump-example1.ogg
dump_file VARCHAR(1024),
-- intro music to play
-- This optional value specifies a mountpoint that clients are automatically moved to
-- if the source shuts down or is not streaming at the time a listener connects.
intro VARCHAR(1024),
fallback_mount VARCHAR(1024),
-- When enabled, this allows a connecting source client or relay on this mountpoint
-- to move listening clients back from the fallback mount.
fallback_override INTEGER DEFAULT 1,
-- When set to 1, this will cause new listeners, when the max listener count for the mountpoint
-- has been reached, to move to the fallback mount if there is one specified.
fallback_when_full BOOLEAN DEFAULT TRUE,
-- When set to 1, this will cause new listeners, when the max listener count for the mountpoint
-- has been reached, to move to the fallback mount if there is one specified.
fallback_when_full INTEGER DEFAULT 1,
--For non-Ogg streams like MP3, the metadata that is inserted into the stream often
-- has no defined character set.
charset VARCHAR(1024) DEFAULT 'ISO8859-1',
-- possible values are -1, 0, 1
-- real name is public but this is reserved word in ruby
is_public INTEGER DEFAULT 0,
--For non-Ogg streams like MP3, the metadata that is inserted into the stream often
-- has no defined character set.
charset VARCHAR(1024) DEFAULT 'ISO8859-1',
-- possible values are -1, 0, 1
-- real name is public but this is reserved word in ruby
is_public INTEGER DEFAULT 0,
stream_name VARCHAR(1024),
stream_description VARCHAR(10000),
-- direct to user page
stream_url VARCHAR(1024),
-- get this from the session info
genre VARCHAR(256),
bitrate INTEGER,
-- real name is type but this is reserved name in ruby
mime_type VARCHAR(64) NOT NULL DEFAULT 'application/ogg' ,
subtype VARCHAR(64) NOT NULL DEFAULT 'vorbis',
stream_name VARCHAR(1024),
stream_description VARCHAR(10000),
-- direct to user page
stream_url VARCHAR(1024),
-- get this from the session info
genre VARCHAR(256),
bitrate INTEGER,
-- real name is type but this is reserved name in ruby
mime_type VARCHAR(64) NOT NULL DEFAULT 'audio/ogg' ,
subtype VARCHAR(64) NOT NULL DEFAULT 'vorbis',
-- This optional setting allows for providing a burst size which overrides the
-- DEFAULT burst size as defined in limits. The value is in bytes.
burst_size INTEGER,
mp3_metadata_interval INTEGER,
-- This optional setting allows for providing a burst size which overrides the
-- DEFAULT burst size as defined in limits. The value is in bytes.
burst_size INTEGER,
mp3_metadata_interval INTEGER,
-- Enable this to prevent this mount from being shown on the xsl pages.
-- This is mainly for cases where a local relay is configured and you do
-- not want the source of the local relay to be shown
hidden BOOLEAN DEFAULT TRUE,
-- Enable this to prevent this mount from being shown on the xsl pages.
-- This is mainly for cases where a local relay is configured and you do
-- not want the source of the local relay to be shown
hidden INTEGER DEFAULT 1,
--called when the source connects or disconnects. The scripts are called with the name of the mount
on_connect VARCHAR(1024),
on_disconnect VARCHAR(1024),
--called when the source connects or disconnects. The scripts are called with the name of the mount
on_connect VARCHAR(1024),
on_disconnect VARCHAR(1024),
-- references icecast_user_authentications(id)
authentication_id varchar(64) DEFAULT NULL,
-- references icecast_user_authentications(id)
authentication_id varchar(64) DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
------stats------
listeners INTEGER NOT NULL DEFAULT 0,
sourced BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE icecast_paths (
id VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
base_dir VARCHAR(1024) NOT NULL DEFAULT './',
log_dir VARCHAR(1024) NOT NULL DEFAULT './logs',
pid_file VARCHAR(1024) DEFAULT './icecast.pid',
web_root VARCHAR(1024) NOT NULL DEFAULT './web',
admin_root VARCHAR(1024) NOT NULL DEFAULT './admin',
allow_ip VARCHAR(1024),
deny_ip VARCHAR(1024),
alias_source VARCHAR(1024),
alias_dest VARCHAR(1024),
id VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
base_dir VARCHAR(1024) NOT NULL DEFAULT './',
log_dir VARCHAR(1024) NOT NULL DEFAULT './logs',
pid_file VARCHAR(1024) DEFAULT './icecast.pid',
web_root VARCHAR(1024) NOT NULL DEFAULT './web',
admin_root VARCHAR(1024) NOT NULL DEFAULT './admin',
allow_ip VARCHAR(1024),
deny_ip VARCHAR(1024),
alias_source VARCHAR(1024),
alias_dest VARCHAR(1024),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
@ -229,7 +233,7 @@ CREATE TABLE icecast_loggings (
playlist_log VARCHAR(1024),
-- 4 Debug, 3 Info, 2 Warn, 1 Error
log_level INTEGER NOT NULL DEFAULT 3 ,
log_archive BOOLEAN,
log_archive INTEGER,
-- 10 meg log file by default
log_size INTEGER DEFAULT 10000,
@ -240,7 +244,7 @@ CREATE TABLE icecast_loggings (
CREATE TABLE icecast_securities (
id VARCHAR(64) PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
chroot BOOLEAN NOT NULL DEFAULT FALSE,
chroot INTEGER NOT NULL DEFAULT 0,
change_owner_user VARCHAR(64),
change_owner_group VARCHAR(64),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -259,13 +263,13 @@ CREATE TABLE icecast_master_server_relays(
-- This is the relay username on the master server. It is used to query the server for a list of
-- mountpoints to relay. If not specified then 'relay' is used
master_username VARCHAR(64) NOT NULL,
master_password VARCHAR(64) NOT NULL,
master_pass VARCHAR(64) NOT NULL,
--Global on-demand setting for relays. Because you do not have individual relay options when
-- using a master server relay, you still may want those relays to only pull the stream when
-- there is at least one listener on the slave. The typical case here is to avoid surplus
-- bandwidth costs when no one is listening.
relays_on_demand BOOLEAN NOT NULL DEFAULT TRUE,
relays_on_demand INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
@ -286,7 +290,7 @@ CREATE TABLE icecast_templates (
location VARCHAR(1024) NOT NULL,
name VARCHAR(256) NOT NULL,
admin_email VARCHAR(1024) NOT NULL DEFAULT 'admin@jamkazam.com',
fileserve BOOLEAN NOT NULL DEFAULT TRUE,
fileserve INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
@ -296,7 +300,7 @@ CREATE TABLE icecast_templates (
CREATE TABLE icecast_servers (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
--use this to mark the server configuration as needing to be regenerated
config_changed BOOLEAN DEFAULT FALSE,
config_changed INTEGER DEFAULT 0,
limit_id VARCHAR(64) REFERENCES icecast_limits(id),
admin_auth_id VARCHAR(64) REFERENCES icecast_admin_authentications(id),
directory_id VARCHAR(64) REFERENCES icecast_directories(id),
@ -319,7 +323,7 @@ CREATE TABLE icecast_servers (
-- All files are served relative to the path specified in the <paths><webroot> configuration
-- setting. By DEFAULT the setting is enabled so that requests for the images
-- on the status page are retrievable.
fileserve BOOLEAN,
fileserve INTEGER,
-- This optional setting allows for the administrator of the server to override the
-- DEFAULT server identification. The DEFAULT is icecast followed by a version number
-- and most will not care to change it however this setting will change that.

View File

@ -51,6 +51,11 @@ message ClientMessage {
MUSICIAN_SESSION_FRESH = 240;
MUSICIAN_SESSION_STALE = 245;
// icecast notifications
SOURCE_UP_REQUESTED = 250;
SOURCE_DOWN_REQUESTED = 255;
TEST_SESSION_MESSAGE = 295;
@ -115,10 +120,13 @@ message ClientMessage {
// band notifications
optional BandInvitation band_invitation = 225;
optional BandInvitationAccepted band_invitation_accepted = 230;
optional MusicianSessionFresh musician_session_fresh = 240;
optional MusicianSessionStale musician_session_stale = 245;
// icecast notifications
optional SourceUpRequested source_up_requested = 250;
optional SourceDownRequested source_down_requested = 255;
// Client-Session messages (to/from)
optional TestSessionMessage test_session_message = 295;
@ -372,6 +380,14 @@ message MusicianSessionStale {
optional string photo_url = 4;
}
message SourceUpRequested {
}
message SourceDownRequested {
}
// route_to: session
// a test message used by ruby-client currently. just gives way to send out to rest of session
message TestSessionMessage {

View File

@ -32,6 +32,7 @@ require "jam_ruby/lib/em_helper.rb"
require "jam_ruby/resque/audiomixer"
require "jam_ruby/resque/icecast_config_writer"
require "jam_ruby/resque/scheduled/audiomixer_retry"
require "jam_ruby/resque/scheduled/icecast_config_retry"
require "jam_ruby/mq_router"
require "jam_ruby/base_manager"
require "jam_ruby/connection_manager"
@ -108,6 +109,10 @@ require "jam_ruby/models/icecast_security"
require "jam_ruby/models/icecast_server"
require "jam_ruby/models/icecast_template"
require "jam_ruby/models/icecast_user_authentication"
require "jam_ruby/models/icecast_server_mount"
require "jam_ruby/models/icecast_server_relay"
require "jam_ruby/models/icecast_server_socket"
require "jam_ruby/models/icecast_template_socket"
include Jampb

View File

@ -1,23 +1,36 @@
module JamRuby
class IcecastAdminAuthentication < ActiveRecord::Base
attr_accessible :source_pass, :relay_user, :relay_pass, :admin_user, :admin_pass, as: :admin
has_many :servers, :class_name => "JamRuby::IcecastServer", :inverse_of => :admin_auth, :foreign_key => "admin_auth_id"
has_many :templates, :class_name => "JamRuby::IcecastTemplate", :inverse_of => :admin_auth, :foreign_key => "admin_auth_id"
validates :source_password, presence: true, length: {minimum: 5}
validates :admin_password, presence: true, length: {minimum: 5}
validates :source_pass, presence: true, length: {minimum: 5}
validates :admin_pass, presence: true, length: {minimum: 5}
validates :relay_user, presence: true, length: {minimum: 5}
validates :relay_password, presence: true, length: {minimum: 5}
validates :relay_pass, presence: true, length: {minimum: 5}
validates :admin_user, presence: true, length: {minimum: 5}
after_save :poke_config
def poke_config
IcecastServer.update(servers, config_changed: 1)
templates.each { |template| IcecastServer.update(template.servers, config_changed: 1) }
end
def to_s
"admin_user=#{admin_user} relay_user=#{relay_user}"
end
def dumpXml (builder)
builder.tag! 'authentication' do |auth|
auth.tag! 'source-password', source_password
auth.tag! 'source-password', source_pass
auth.tag! 'admin-user', admin_user
auth.tag! 'relay-user', relay_user
auth.tag! 'relay-password', relay_password
auth.tag! 'admin-password', admin_password
auth.tag! 'relay-password', relay_pass
auth.tag! 'admin-password', admin_pass
end
end
end

View File

@ -1,12 +1,25 @@
module JamRuby
class IcecastDirectory < ActiveRecord::Base
attr_accessible :yp_url_timeout, :yp_url, as: :admin
has_many :servers, :class_name => "JamRuby::IcecastServer", :inverse_of => :directory, :foreign_key => "directory_id"
has_many :templates, :class_name => "JamRuby::IcecastDirectory", :inverse_of => :directory, :foreign_key => "directory_id"
has_many :templates, :class_name => "JamRuby::IcecastTemplate", :inverse_of => :directory, :foreign_key => "directory_id"
validates :yp_url_timeout, presence: true, numericality: {only_integer: true}, length: {in: 1..30}
validates :yp_url, presence: true
after_save :poke_config
def poke_config
IcecastServer.update(servers, config_changed: 1)
templates.each { |template| IcecastServer.update(template.servers, config_changed: 1) }
end
def to_s
yp_url
end
def dumpXml (builder)
builder.tag! 'directory' do |dir|

View File

@ -1,6 +1,9 @@
module JamRuby
class IcecastLimit < ActiveRecord::Base
attr_accessible :clients, :sources, :queue_size, :client_timeout, :header_timeout, :source_timeout, :burst_size,
as: :admin
has_many :servers, class_name: 'JamRuby::IcecastServer', inverse_of: :limit, foreign_key: 'limit_id'
has_many :templates, class_name: 'JamRuby::IcecastTemplate', inverse_of: :limit, foreign_key: 'limit_id'
@ -12,6 +15,17 @@ module JamRuby
validates :source_timeout, presence: true, numericality: {only_integer: true}
validates :burst_size, presence: true, numericality: {only_integer: true}
after_save :poke_config
def poke_config
IcecastServer.update(servers, config_changed: 1)
templates.each { |template| IcecastServer.update(template.servers, config_changed: 1) }
end
def to_s
"clients=#{clients} sources=#{sources}"
end
def dumpXml (builder)
builder.tag! 'limits' do |limits|

View File

@ -1,18 +1,34 @@
module JamRuby
class IcecastListenSocket < ActiveRecord::Base
belongs_to :server, :class_name => "JamRuby::IcecastServer" , :inverse_of => :sockets
has_and_belongs_to_many :servers, :class_name => "JamRuby::IcecastServer", :join_table => "icecast_server_sockets"
attr_accessible :port, :bind_address, :shoutcast_mount, :shoutcast_compat, as: :admin
has_many :server_sockets, :class_name => "JamRuby::IcecastServerSocket", :inverse_of => :socket, :foreign_key => 'icecast_listen_socket_id'
has_many :servers, :class_name => "JamRuby::IcecastServer", :through => :server_sockets
has_many :template_sockets, :class_name => "JamRuby::IcecastTemplateSocket", :inverse_of => :socket, :foreign_key => 'icecast_listen_socket_id'
has_many :templates, :class_name => "JamRuby::IcecastTemplate", :through => :template_sockets
validates :port, presence: true, numericality: {only_integer: true}, length: {in: 1..65535}
validates :shoutcast_compat, :inclusion => {:in => [nil, true, false]}
validates :shoutcast_compat, :inclusion => {:in => [nil, 0, 1]}
after_save :poke_config
def poke_config
IcecastServer.update(servers, config_changed: 1)
templates.each { |template| IcecastServer.update(template.servers, config_changed: 1) }
end
def to_s
"port=#{port} bind_address=#{bind_address}"
end
def dumpXml (builder)
builder.tag! 'listen-socket' do |listen|
listen.tag! 'port', port
listen.tag! 'bind-address', bind_address if bind_address
listen.tag! 'bind-address', bind_address if !bind_address.nil? && !bind_address.empty?
listen.tag! 'shoutcast-mount', shoutcast_mount if shoutcast_mount
listen.tag! 'shoutcast-compat', shoutcast_compat ? 1 : 0 unless shoutcast_compat.nil?
listen.tag! 'shoutcast-compat', shoutcast_compat if shoutcast_compat
end
end
end

View File

@ -1,22 +1,34 @@
module JamRuby
class IcecastLogging < ActiveRecord::Base
attr_accessible :access_log, :error_log, :playlist_log, :log_level, :log_archive, :log_size, as: :admin
has_many :servers, :class_name => "JamRuby::IcecastServer", :inverse_of => :logging, :foreign_key => "logging_id"
has_many :templates, :class_name => "JamRuby::IcecastTemplate", :inverse_of => :logging, :foreign_key => "logging_id"
validates :access_log, presence: true
validates :error_log, presence: true
validates :log_level, :inclusion => {:in => [1, 2, 3, 4]}
validates :log_archive, :inclusion => {:in => [nil, true, false]}
validates :log_archive, :inclusion => {:in => [nil, 0, 1]}
validates :log_size, numericality: {only_integer: true}, if: lambda {|m| m.log_size.present?}
after_save :poke_config
def poke_config
IcecastServer.update(servers, config_changed: 1)
templates.each { |template| IcecastServer.update(template.servers, config_changed: 1) }
end
def to_s
"access_log=#{access_log} error_log=#{error_log} log_level=#{log_level}"
end
def dumpXml(builder)
builder.tag! 'logging' do |log|
log.tag! 'accesslog', access_log
log.tag! 'errorlog', error_log
log.tag! 'playlistlog', playlist_log if playlist_log
log.tag! 'playlistlog', playlist_log if !playlist_log.nil? && !playlist_log.empty?
log.tag! 'logsize', log_size if log_size
log.tag! 'logarchive', log_archive ? '1' : '0' unless log_archive.nil?
log.tag! 'logarchive', log_archive if log_archive
log.tag! 'loglevel', log_level
end
end

View File

@ -1,6 +1,9 @@
module JamRuby
class IcecastMasterServerRelay < ActiveRecord::Base
attr_accessible :master_server, :master_server_port, :master_update_interval, :master_username, :master_pass,
:relays_on_demand, as: :admin
has_many :servers, :class_name => "JamRuby::IcecastServer", :inverse_of => :master_relay, :foreign_key => "master_relay_id"
has_many :templates, :class_name => "JamRuby::IcecastTemplate", :inverse_of => :master_relay, :foreign_key => "master_relay_id"
@ -8,16 +11,27 @@ module JamRuby
validates :master_server_port, presence: true, numericality: {only_integer: true}, length: {in: 1..65535}
validates :master_update_interval, presence: true, numericality: {only_integer: true}, length: {in: 1..1200}
validates :master_username, presence: true, length: {minimum: 5}
validates :master_password, presence: true, length: {minimum: 5}
validates :relays_on_demand, :inclusion => {:in => [true, false]}
validates :master_pass, presence: true, length: {minimum: 5}
validates :relays_on_demand, :inclusion => {:in => [0, 1]}
after_save :poke_config
def poke_config
IcecastServer.update(servers, config_changed: 1)
templates.each { |template| IcecastServer.update(template.servers, config_changed: 1) }
end
def to_s
"master_server=#{master_server} master_server_port=#{master_server_port} master_username=#{master_username}"
end
def dumpXml(builder)
builder.tag! 'master-server', master_server
builder.tag! 'master-server-port', master_server_port
builder.tag! 'master-update-interval', master_update_interval
builder.tag! 'master-username', master_username
builder.tag! 'master-password', master_password
builder.tag! 'relays-on-demand', relays_on_demand ? '1' : '0'
builder.tag! 'master-password', master_pass
builder.tag! 'relays-on-demand', relays_on_demand
end
end
end

View File

@ -1,16 +1,23 @@
module JamRuby
class IcecastMount < ActiveRecord::Base
belongs_to :authentication, class_name: "JamRuby::IcecastUserAuthentication", inverse_of: :mount
has_and_belongs_to_many :servers, :class_name => "JamRuby::IcecastServer", :join_table => "icecast_server_mounts"
attr_accessible :authentication_id, :name, :source_username, :source_pass, :max_listeners, :max_listener_duration,
:dump_file, :intro, :fallback_mount, :fallback_override, :fallback_when_full, :charset, :is_public,
:stream_name, :stream_description, :stream_url, :genre, :bitrate, :mime_type, :subtype, :burst_size,
:mp3_metadata_interval, :hidden, :on_connect, :on_disconnect, as: :admin
validates :mount_name, presence: true
validates :username, length: {minimum: 5}, if: lambda {|m| m.username.present?}
validates :password, length: {minimum: 5}, if: lambda {|m| m.password.present?}
belongs_to :authentication, class_name: "JamRuby::IcecastUserAuthentication", inverse_of: :mount, :foreign_key => 'authentication_id'
has_many :server_mounts, :class_name => "JamRuby::IcecastServerMount", :inverse_of => :mount, :foreign_key => 'icecast_mount_id'
has_many :servers, :class_name => "JamRuby::IcecastServer", :through => :server_mounts, :source => :server
validates :name, presence: true
validates :source_username, length: {minimum: 5}, if: lambda {|m| m.source_username.present?}
validates :source_pass, length: {minimum: 5}, if: lambda {|m| m.source_pass.present?}
validates :max_listeners, length: {in: 1..15000}, if: lambda {|m| m.max_listeners.present?}
validates :max_listener_duration, length: {in: 1..3600 * 48}, if: lambda {|m| m.max_listener_duration.present?}
validates :fallback_override, :inclusion => {:in => [true, false]} , if: lambda {|m| m.fallback_mount.present?}
validates :fallback_when_full, :inclusion => {:in => [true, false]} , if: lambda {|m| m.fallback_mount.present?}
validates :fallback_override, :inclusion => {:in => [0, 1]} , if: lambda {|m| m.fallback_mount.present?}
validates :fallback_when_full, :inclusion => {:in => [0, 1]} , if: lambda {|m| m.fallback_mount.present?}
validates :is_public, presence: true, :inclusion => {:in => [-1, 0, 1]}
validates :stream_name, presence: true
validates :stream_description, presence: true
@ -21,33 +28,82 @@ module JamRuby
validates :subtype, presence: true
validates :burst_size, numericality: {only_integer: true}, if: lambda {|m| m.burst_size.present?}
validates :mp3_metadata_interval, numericality: {only_integer: true}, if: lambda {|m| m.mp3_metadata_interval.present?}
validates :hidden, presence: true, :inclusion => {:in => [true, false]}
validates :hidden, :inclusion => {:in => [0, 1]}
validate :name_has_correct_format
before_save :sanitize_active_admin
after_save :after_save
after_commit :after_commit
def name_has_correct_format
errors.add(:name, "must start with /") unless name && name.start_with?('/')
end
def after_save
IcecastServer.update(servers, config_changed: 1)
# transiting to sourced from not sourced
if !sourced_was && sourced
end
end
def sanitize_active_admin
self.authentication_id = nil if self.authentication_id == ''
end
def source_up
with_lock do
self.sourced = true
self.save(:validate => false)
end
end
def source_down
with_lock do
sourced = false
save(:validate => false)
end
end
def listener_add
with_lock do
increment!(:listeners)
end
end
def listener_remove
with_lock do
decrement!(:listeners)
end
end
def dumpXml(builder)
builder.tag! 'mount' do |mount|
mount.tag! 'mount-name', mount_name
mount.tag! 'username', username if username
mount.tag! 'password', password if password
mount.tag! 'max-listeners', max_listeners if max_listeners
mount.tag! 'max-listener-duration', max_listener_duration if max_listener_duration
mount.tag! 'dump-file', dump_file if dump_file
mount.tag! 'intro', intro if intro
mount.tag! 'fallback-mount', fallback_mount if fallback_mount
mount.tag! 'fallback-override', fallback_override ? '1' : '0' unless fallback_override.nil?
mount.tag! 'fallback-when-full', fallback_when_full ? '1' : '0' unless fallback_when_full.nil?
mount.tag! 'mount-name', name
mount.tag! 'username', source_username if !source_username.nil? && !source_username.empty?
mount.tag! 'password', source_pass if !source_pass.nil? && !source_pass.empty?
mount.tag! 'max-listeners', max_listeners unless max_listeners.nil?
mount.tag! 'max-listener-duration', max_listener_duration unless max_listener_duration.nil?
mount.tag! 'dump-file', dump_file if !dump_file.nil? && !dump_file.empty?
mount.tag! 'intro', intro if !intro.nil? && !intro.empty?
mount.tag! 'fallback-mount', fallback_mount if !fallback_mount.nil? && !fallback_mount.empty?
mount.tag! 'fallback-override', fallback_override if fallback_override
mount.tag! 'fallback-when-full', fallback_when_full if fallback_when_full
mount.tag! 'charset', charset if charset
mount.tag! 'public', is_public
mount.tag! 'stream-name', stream_name if stream_name
mount.tag! 'stream-description', stream_description if stream_description
mount.tag! 'stream-url', stream_url if stream_url
mount.tag! 'genre', genre if genre
mount.tag! 'stream-name', stream_name if !stream_name.nil? && !stream_name.empty?
mount.tag! 'stream-description', stream_description if !stream_description.nil? && !stream_description.empty?
mount.tag! 'stream-url', stream_url if !stream_url.nil? && !stream_url.empty?
mount.tag! 'genre', genre unless genre.empty?
mount.tag! 'bitrate', bitrate if bitrate
mount.tag! 'type', mime_type
mount.tag! 'subtype', subtype
mount.tag! 'burst-size', burst_size if burst_size
mount.tag! 'mp3-metadata-interval', mp3_metadata_interval if mp3_metadata_interval
mount.tag! 'hidden', hidden ? '1' : '0'
mount.tag! 'mp3-metadata-interval', mp3_metadata_interval unless mp3_metadata_interval.nil?
mount.tag! 'hidden', hidden
mount.tag! 'on-connect', on_connect if on_connect
mount.tag! 'on-disconnect', on_disconnect if on_disconnect
@ -59,7 +115,7 @@ module JamRuby
def get_media_url
raise "Unassociated server to mount" if self.server_mount.nil?
"http://" + server_mount.server.hostname + self.mount_name
"http://" + server_mount.server.hostname + self.name
end
end
end

View File

@ -1,6 +1,9 @@
module JamRuby
class IcecastPath < ActiveRecord::Base
attr_accessible :base_dir, :log_dir, :pid_file, :web_root, :admin_root, :allow_ip, :deny_ip, :alias_source,
:alias_dest, as: :admin
has_many :servers, :class_name => "JamRuby::IcecastServer", :inverse_of => :path, :foreign_key => "path_id"
has_many :templates, :class_name => "JamRuby::IcecastTemplate", :inverse_of => :path, :foreign_key => "path_id"
@ -9,17 +12,28 @@ module JamRuby
validates :web_root, presence: true
validates :admin_root, presence: true
after_save :poke_config
def poke_config
IcecastServer.update(servers, config_changed: 1)
templates.each { |template| IcecastServer.update(template.servers, config_changed: 1) }
end
def to_s
"base_dir=#{base_dir}"
end
def dumpXml (builder)
builder.tag! 'paths' do |paths|
paths.tag! 'basedir', base_dir
paths.tag! 'logdir', log_dir
paths.tag! 'pidfile', pid_file if pid_file
paths.tag! 'pidfile', pid_file if !pid_file.nil? && !pid_file.empty?
paths.tag! 'webroot', web_root
paths.tag! 'adminroot', admin_root
paths.tag! 'allow-ip', allow_ip if allow_ip
paths.tag! 'deny-ip', deny_ip if deny_ip
paths.tag! 'alias', :source => alias_source, :dest => alias_dest if alias_source
paths.tag! 'alias', :source => alias_source, :dest => alias_dest if !alias_source.nil? && !alias_source.empty?
end
end
end

View File

@ -1,24 +1,38 @@
module JamRuby
class IcecastRelay < ActiveRecord::Base
has_and_belongs_to_many :servers, :class_name => "JamRuby::IcecastServer", :join_table => "icecast_server_relays"
attr_accessible :server, :port, :mount, :local_mount, :relay_username, :relay_pass, :relay_shoutcast_metadata, :on_demand,
as: :admin
has_many :server_relays, :class_name => "JamRuby::IcecastServerRelay"
has_many :servers, :class_name => "JamRuby::IcecastServer", :through => :server_relays, :source => :server
validates :port, presence: true, numericality: {only_integer: true}, length: {in: 1..65535}
validates :mount, presence: true
validates :server, presence: true
validates :relay_shoutcast_metadata, :inclusion => {:in => [true, false]}
validates :on_demand, presence: true, :inclusion => {:in => [true, false]}
validates :relay_shoutcast_metadata, :inclusion => {:in => [0, 1]}
validates :on_demand, presence: true, :inclusion => {:in => [0, 1]}
after_save :poke_config
def poke_config
IcecastServer.update(servers, :config_changed => true)
end
def to_s
mount
end
def dumpXml (builder)
builder.tag! 'relay' do |listen|
listen.tag! 'server', server
listen.tag! 'port', port
listen.tag! 'mount', mount
listen.tag! 'local-mount', local_mount if local_mount
listen.tag! 'username', username if username
listen.tag! 'password', password if password
listen.tag! 'relay-shoutcast-metadata', relay_shoutcast_metadata ? 1 : 0
listen.tag! 'on-demand', on_demand ? 1 : 0
listen.tag! 'local-mount', local_mount if !local_mount.nil? && !local_mount.empty?
listen.tag! 'username', relay_username if !relay_username.nil? && !relay_username.empty?
listen.tag! 'password', relay_pass if !relay_pass.nil? && !pasword.empty?
listen.tag! 'relay-shoutcast-metadata', relay_shoutcast_metadata
listen.tag! 'on-demand', on_demand
end
end
end

View File

@ -1,14 +1,27 @@
module JamRuby
class IcecastSecurity < ActiveRecord::Base
attr_accessible :chroot, :change_owner_user, :change_owner_group, as: :admin
has_many :servers, :class_name => "JamRuby::IcecastServer", :inverse_of => :security, :foreign_key => "security_id"
has_many :templates, :class_name => "JamRuby::IcecastTemplate", :inverse_of => :security, :foreign_key => "security_id"
validates :chroot, :inclusion => {:in => [true, false]}
validates :chroot, :inclusion => {:in => [0, 1]}
after_save :poke_config
def poke_config
IcecastServer.update(servers, config_changed: 1)
templates.each { |template| IcecastServer.update(template.servers, config_changed: 1) }
end
def to_s
"chroot=#{chroot} change_owner_user=#{change_owner_user} change_owner_group=#{change_owner_group}"
end
def dumpXml(builder)
builder.tag! 'security' do |security|
security.tag! 'chroot', chroot ? '1' : '0'
security.tag! 'chroot', chroot
if change_owner_user
security.tag! 'changeowner' do
security.tag! 'user', change_owner_user

View File

@ -1,6 +1,11 @@
module JamRuby
class IcecastServer < ActiveRecord::Base
attr_accessor :skip_config_changed_flag
attr_accessible :template_id, :limit_id, :admin_auth_id, :directory_id, :master_relay_id, :path_id, :logging_id,
:security_id, :config_changed, :hostname, :location, :admin_email, :fileserve, as: :admin
belongs_to :template, :class_name => "JamRuby::IcecastTemplate", foreign_key: 'template_id', :inverse_of => :servers
# all are overrides, because the template defines all of these as well. When building the XML, we will prefer these if set
@ -11,29 +16,79 @@ module JamRuby
belongs_to :path, :class_name => "JamRuby::IcecastPath", foreign_key: 'path_id', :inverse_of => :servers
belongs_to :logging, :class_name => "JamRuby::IcecastLogging", foreign_key: 'logging_id', :inverse_of => :servers
belongs_to :security, :class_name => "JamRuby::IcecastSecurity", foreign_key: 'security_id', :inverse_of => :servers
has_and_belongs_to_many :listen_sockets, :class_name => "JamRuby::IcecastListenSocket", :join_table => "icecast_server_sockets"
has_many :listen_socket_servers, :class_name => "JamRuby::IcecastServerSocket", :inverse_of => :server
has_many :listen_sockets, :class_name => "JamRuby::IcecastListenSocket", :through => :listen_socket_servers, :source => :socket
# mounts and relays are naturally server-specific, though
has_and_belongs_to_many :mounts, :class_name => "JamRuby::IcecastMount", :join_table => "icecast_server_mounts"
has_and_belongs_to_many :relays, :class_name => "JamRuby::IcecastRelay", :join_table => "icecast_server_relays"
has_many :server_mounts, :class_name => "JamRuby::IcecastServerMount", :inverse_of => :server
has_many :mounts, :class_name => "JamRuby::IcecastMount", :through => :server_mounts, :source => :mount
validates :config_changed, :inclusion => {:in => [true, false]}
has_many :server_relays, :class_name => "JamRuby::IcecastServerRelay", :inverse_of => :relay
has_many :relays, :class_name => "JamRuby::IcecastRelay", :through => :server_relays, :source => :relay
validates :config_changed, :inclusion => {:in => [0, 1]}
validates :hostname, presence: true
validates :fileserve, :inclusion => {:in => [true, false]}, :if => lambda {|s| s.fileserve.present? }
validates :fileserve, :inclusion => {:in => [0, 1]}, :if => lambda {|s| s.fileserve.present? }
validates :server_id, presence: true
validates :template, presence: true
before_save :before_save, unless: lambda { skip_config_changed_flag }
before_save :sanitize_active_admin
after_save :after_save
def before_save
self.config_changed = 1
end
def sanitize_active_admin
self.template_id = nil if self.template_id == ''
self.limit_id = nil if self.limit_id == ''
self.admin_auth_id = nil if self.admin_auth_id == ''
self.directory_id = nil if self.directory_id == ''
self.master_relay_id = nil if self.master_relay_id == ''
self.path_id = nil if self.path_id == ''
self.logging_id = nil if self.logging_id == ''
self.security_id = nil if self.security_id == ''
end
def after_save
# if we set config_changed, then queue up a job
if config_changed_was == 0 && config_changed == 1
IcecastConfigWriter.enqueue(self.server_id)
end
end
# this method is the correct way to set config_changed to false
# if you don't do it this way, then likely you'll get into a loop
# config_changed = true, enqueued job, job executes, job accidentally flags config_changed by touching the model, and repeat
def config_updated
self.skip_config_changed_flag = true
self.config_changed = 0
begin
self.save!
rescue
raise
ensure
self.skip_config_changed_flag = false
end
end
def to_s
return server_id
end
def dumpXml (output=$stdout, indent=1)
builder = ::Builder::XmlMarkup.new(:target => output, :indent => indent)
builder.tag! 'icecast' do |icecast|
icecast.tag! 'hostname', hostname
icecast.tag! 'location', location.nil? ? template.location : location
icecast.tag! 'server-id', server_id
icecast.tag! 'admin', admin_email ? admin_email : template.admin_email
icecast.tag! 'fileserve', fileserve.nil? ? (template.fileserve ? 1 : 0) : (fileserve ? 1 : 0)
builder.tag! 'icecast' do |root|
root.tag! 'hostname', hostname
root.tag! 'location', (location.nil? || location.empty?) ? template.location : location
root.tag! 'server-id', server_id
root.tag! 'admin', (admin_email.nil? || admin_email.empty?) ? template.admin_email : admin_email
root.tag! 'fileserve', fileserve.nil? ? template.fileserve : fileserve
# do we have an override specified? or do we go with the template
current_limit = limit ? limit : template.limit

View File

@ -1,12 +1,13 @@
module JamRuby
class IcecastServerMount < ActiveRecord::Base
self.table_name = 'icecast_server_mounts'
self.primary_key = 'id'
attr_accessible :icecast_mount_id, :icecast_server_id, as: :admin
belongs_to :mount, :class_name => "JamRuby::IcecastMount" , :inverse_of => :server_mount
belongs_to :server, :class_name => "JamRuby::IcecastServer"
belongs_to :mount, :class_name => "JamRuby::IcecastMount", :foreign_key => 'icecast_mount_id', :inverse_of => :server_mounts
belongs_to :server, :class_name => "JamRuby::IcecastServer", :foreign_key => 'icecast_server_id', :inverse_of => :server_mounts
validates :server_id, :presence => true
validates :mount_id, :presence => true
validates :server, :presence => true
validates :mount, :presence => true
end
end

View File

@ -1,13 +1,15 @@
module JamRuby
class IcecastServerRelay < ActiveRecord::Base
self.primary_key = 'id'
self.table_name = 'icecast_server_relays'
belongs_to :relay, :class_name => "JamRuby::IcecastRelay"
belongs_to :server, :class_name => "JamRuby::IcecastServer"
attr_accessible :icecast_relay_id, :icecast_server_id, as: :admin
validates :server_id, :presence => true
validates :relay_id, :presence => true
belongs_to :relay, :class_name => "JamRuby::IcecastRelay", :foreign_key => 'icecast_relay_id', :inverse_of => :server_relays
belongs_to :server, :class_name => "JamRuby::IcecastServer", :foreign_key => 'icecast_server_id', :inverse_of => :server_relays
validates :server, :presence => true
validates :relay, :presence => true
end

View File

@ -1,13 +1,15 @@
module JamRuby
class IcecastServerSocket < ActiveRecord::Base
self.primary_key = 'id'
self.table_name = 'icecast_server_sockets'
belongs_to :socket, :class_name => "JamRuby::IcecastListenSocket"
belongs_to :server, :class_name => "JamRuby::IcecastServer"
attr_accessible :icecast_listen_socket_id, :icecast_server_id, as: :admin
validates :socket_id, :presence => true
validates :server_id, :presence => true
belongs_to :socket, :class_name => "JamRuby::IcecastListenSocket", :foreign_key => 'icecast_listen_socket_id', :inverse_of => :server_sockets
belongs_to :server, :class_name => "JamRuby::IcecastServer", :foreign_key => 'icecast_server_id', :inverse_of => :listen_socket_servers
validates :socket, :presence => true
validates :server, :presence => true
end
end

View File

@ -1,6 +1,8 @@
module JamRuby
class IcecastTemplate < ActiveRecord::Base
attr_accessible :limit_id, :admin_auth_id, :directory_id, :master_relay_id, :path_id, :logging_id,
:security_id, :name, :location, :admin_email, :fileserve, as: :admin
belongs_to :limit, :class_name => "JamRuby::IcecastLimit", foreign_key: 'limit_id', :inverse_of => :templates
belongs_to :admin_auth, :class_name => "JamRuby::IcecastAdminAuthentication", foreign_key: 'admin_auth_id', :inverse_of => :templates
@ -11,12 +13,17 @@ module JamRuby
belongs_to :security, :class_name => "JamRuby::IcecastSecurity", foreign_key: 'security_id', :inverse_of => :templates
has_many :servers, :class_name => "JamRuby::IcecastServer", :inverse_of => :template, :foreign_key => "template_id"
has_and_belongs_to_many :listen_sockets, :class_name => "JamRuby::IcecastListenSocket", :join_table => "icecast_template_sockets"
#has_many :server_mounts, class_name: "JamRuby::IcecastServerMount", :inverse_of => :mount, :foreign_key
#has_many :mounts, class_name: "JamRuby::IcecastMount", through: :server_mounts, :source => :template
has_many :listen_socket_templates, :class_name => "JamRuby::IcecastTemplateSocket", :inverse_of => :template, :foreign_key => 'icecast_template_id'
has_many :listen_sockets, :class_name => "JamRuby::IcecastListenSocket", :through => :listen_socket_templates , :source => :socket
validates :name, presence: true
validates :location, presence: true
validates :admin_email, presence: true
validates :fileserve, :inclusion => {:in => [true, false]}
validates :fileserve, :inclusion => {:in => [0, 1]}
validates :limit, presence: true
validates :admin_auth, presence: true
@ -24,5 +31,23 @@ module JamRuby
validates :logging, presence: true
validates :security, presence: true
validates :listen_sockets, length: {minimum: 1}
before_save :sanitize_active_admin
after_save :poke_config
def poke_config
IcecastServer.update(servers, config_changed: 1)
end
def sanitize_active_admin
self.limit_id = nil if self.limit_id == ''
self.admin_auth_id = nil if self.admin_auth_id == ''
self.directory_id = nil if self.directory_id == ''
self.master_relay_id = nil if self.master_relay_id == ''
self.path_id = nil if self.path_id == ''
self.logging_id = nil if self.logging_id == ''
self.security_id = nil if self.security_id == ''
end
end
end

View File

@ -0,0 +1,15 @@
module JamRuby
class IcecastTemplateSocket < ActiveRecord::Base
self.table_name = 'icecast_template_sockets'
attr_accessible :icecast_listen_socket_id, :icecast_template_id, as: :admin
belongs_to :socket, :class_name => "JamRuby::IcecastListenSocket", :foreign_key => 'icecast_listen_socket_id', :inverse_of => :template_sockets
belongs_to :template, :class_name => "JamRuby::IcecastTemplate", :foreign_key => 'icecast_template_id', :inverse_of => :listen_socket_templates
validates :socket, :presence => true
validates :template, :presence => true
end
end

View File

@ -1,12 +1,15 @@
module JamRuby
class IcecastUserAuthentication < ActiveRecord::Base
has_one :mount, class_name: 'JamRuby::IcecastMount', inverse_of: :authentication
attr_accessible :authentication_type, :filename, :allow_duplicate_users, :mount_add, :mount_remove, :listener_add,
:listener_remove, :unused_username, :unused_pass, :auth_header, :timelimit_header, as: :admin
has_one :mount, class_name: 'JamRuby::IcecastMount', inverse_of: :authentication, :foreign_key => 'authentication_id'
validates :authentication_type, presence: true, :inclusion => {:in => ["url", "htpasswd"]}
validates :allow_duplicate_users, :inclusion => {:in => [true, false]}, if: :htpasswd_auth?
validates :username, length: {minimum: 5}, if: :url_auth?
validates :password, length: {minimum: 5}, if: :url_auth?
validates :allow_duplicate_users, :inclusion => {:in => [0, 1]}, if: :htpasswd_auth?
validates :unused_username, length: {minimum: 5}, if: :url_auth_and_user_present?
validates :unused_pass, length: {minimum: 5}, if: :url_auth_and_pass_present?
validates :mount_add, presence: true, if: :url_auth?
validates :mount_remove, presence: true, if: :url_auth?
validates :listener_add, presence: true, if: :url_auth?
@ -14,17 +17,26 @@ module JamRuby
validates :auth_header, presence: true, if: :url_auth?
validates :timelimit_header, presence: true, if: :url_auth?
after_save :poke_config
def poke_config
IcecastServer.update(mount.servers, config_changed: 1) if mount
end
def to_s
"mount=#{mount} username=#{unused_username} auth_header=#{auth_header} timelimit_header=#{timelimit_header}"
end
def dumpXml (builder)
builder.tag! 'authentication', type: authentication_type do |auth|
auth.tag! 'option', name: 'mount_add', value: mount_add if mount_add
auth.tag! 'option', name: 'mount_remove', value: mount_remove if mount_remove
auth.tag! 'option', name: 'username', value: username if username
auth.tag! 'option', name: 'password', value: password if password
auth.tag! 'option', name: 'listener_add', value: listener_add if listener_add
auth.tag! 'option', name: 'listener_remove', value: listener_remove if listener_remove
auth.tag! 'option', name: 'auth_header', value: auth_header if auth_header
auth.tag! 'option', name: 'timelimit_header', value: timelimit_header if timelimit_header
auth.tag! 'option', name: 'mount_add', value: mount_add if !mount_add.nil? && !mount_remove.empty?
auth.tag! 'option', name: 'mount_remove', value: mount_remove if !mount_remove.nil? && !mount_remove.empty?
auth.tag! 'option', name: 'username', value: unused_username if !unused_username.nil? && !unused_username.empty?
auth.tag! 'option', name: 'password', value: unused_pass if !unused_pass.nil? && !unused_pass.empty?
auth.tag! 'option', name: 'listener_add', value: listener_add if !listener_add.nil? && !listener_add.empty?
auth.tag! 'option', name: 'listener_remove', value: listener_remove if !listener_remove.nil? && !listener_remove.empty?
auth.tag! 'option', name: 'auth_header', value: auth_header if !auth_header.nil? && !auth_header.empty?
auth.tag! 'option', name: 'timelimit_header', value: timelimit_header if !timelimit_header.nil? && !timelimit_header.empty?
end
end
@ -36,6 +48,13 @@ module JamRuby
authentication_type == 'url'
end
def url_auth_and_user_present?
url_auth? && self.unused_username.present?
end
def url_auth_and_pass_present?
url_auth? && self.unused_pass.present?
end
end
end

View File

@ -33,6 +33,7 @@ module JamRuby
false
end
# avoid db validations
Mix.where(:id => self.id).update_all(:started_at => Time.now)
true
@ -94,12 +95,6 @@ module JamRuby
s3_manager.sign_url(self.url, {:expires => expiration_time, :content_type => 'audio/ogg', :secure => false}, :put)
end
def self.queue_jobs_needing_retry
Mix.find_each(:conditions => 'should_retry = TRUE or started_at is NULL', :batch_size => 100) do |mix|
mix.enqueue
end
end
private
def delete_s3_files

View File

@ -27,6 +27,12 @@ module JamRuby
end
def self.queue_jobs_needing_retry
Mix.find_each(:conditions => 'should_retry = TRUE or started_at is NULL', :batch_size => 100) do |mix|
mix.enqueue
end
end
def initialize
#@s3_manager = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
end

View File

@ -9,32 +9,60 @@ module JamRuby
# executes a mix of tracks, creating a final output mix
class IcecastConfigWriter
@queue = :icecast_config_writer
@@log = Logging.logger[IcecastConfigWriter]
attr_accessor :icecast_server_id
def self.queue
queue_name(::APP_CONFIG.icecast_server_id)
end
def self.queue_jobs_needing_retry
# if we haven't seen updated_at be tickled in 5 minutes, but config_changed is still set to TRUE, this record has gotten stale
IcecastServer.find_each(:conditions => "config_changed = 1 AND updated_at < (NOW() - interval '#{APP_CONFIG.icecast_max_missing_check} second')", :batch_size => 100) do |server|
IcecastConfigWriter.enqueue(server.server_id)
end
end
def self.queue_name(server_id)
"icecast-#{server_id}"
end
def self.perform(icecast_server_id)
icecast = IcecastConfigWriter.new()
icecast.icecast_server_id = icecast_server_id
icecast.run
end
def self.enqueue(server_id)
begin
Resque.enqueue_to(queue_name(server_id), IcecastConfigWriter, server_id)
return true
rescue
@@log.error("unable to enqueue IceastConfigWriter(#{server_id}). #{$!}")
# implies redis is down
return false
end
end
def initialize
end
def validate
raise "icecast_server_id not spceified" unless icecast_server_id
raise "queue routing mismatch error" unless icecast_server_id == APP_CONFIG.icecast_server_id
end
def execute(cmd)
system cmd
$?.exitstatus
end
def reload
cmd = APP_CONFIG.icecast_reload_cmd
system(cmd)
raise "unable to execute icecast reload cmd=#{cmd}. result=#{$?}" unless $? == 0
result = execute(cmd)
raise "unable to execute icecast reload cmd=#{cmd}. result=#{$?}" unless result == 0
end
def run
@ -43,33 +71,40 @@ module JamRuby
config_file = APP_CONFIG.icecast_config_file
# check if the config file is there at all; if it's not, we need to generate it regardless if config has changed
query = {id: icecast_server_id}
query[:config_changed] = true if File.exist? config_file
query = {server_id: icecast_server_id}
icecast_server = IcecastServer.where(query).first
icecast_server = IcecastServer.where(server_id: icecast_server_id).first
icecast_server.with_lock do
raise "can not find icecast server with query #{query}" unless icecast_server
icecast_server.validate
# don't try to write to the file if for some reason the model isn't valid
# this could happen if an admin mucks around in the db directly
raise "icecast_server.id=#{icecast_server.server_id} not valid. errors=#{icecast_server.errors.inspect}" unless icecast.server.valid?
if File.exist?(config_file) && !icecast_server.config_changed
@@log.info("config not changed. skipping run for server: #{icecast_server.server_id}")
else
icecast_server.with_lock do
# write the new config to a temporary location
tmp_config = Dir::Tmpname.make_tmpname(["#{Dir.tmpdir}/icecast", 'xml'], nil)
File.open(tmp_config, 'w') {
icecast_server.dumpXml(output=tmp_config)
}
# don't try to write to the file if for some reason the model isn't valid
# this could happen if an admin mucks around in the db directly
raise "icecast_server.id=#{icecast_server.server_id} not valid. errors=#{icecast_server.errors.inspect}" unless icecast_server.valid?
# if written successfully, overwrite the current file
FileUtils.mv tmp_config, config_file
# write the new config to a temporary location
tmp_config = Dir::Tmpname.make_tmpname(["#{Dir.tmpdir}/icecast", '.xml'], nil)
# reload server
reload
File.open(tmp_config, 'w') do |f|
icecast_server.dumpXml(f)
end
icecast_server.config_changed = false
icecast_server.save!
# if written successfully, overwrite the current file
FileUtils.mv tmp_config, config_file
# reload server
reload
icecast_server.config_updated
end
end
@@log.info("successful update of config for server: #{icecast_server.server_id}")
end
end
end

View File

@ -14,7 +14,7 @@ module JamRuby
@@log = Logging.logger[AudioMixerRetry]
def self.perform
Mix.queue_jobs_needing_retry
AudioMixer.queue_jobs_needing_retry
end
end

View File

@ -0,0 +1,21 @@
require 'json'
require 'resque'
require 'resque-retry'
require 'net/http'
require 'digest/md5'
module JamRuby
# periodically scheduled to find jobs that need retrying
class IcecastConfigRetry
@queue = :icecast_config_retry
@@log = Logging.logger[IcecastConfigRetry]
def self.perform
IcecastConfigWriter.queue_jobs_needing_retry
end
end
end

View File

@ -151,11 +151,11 @@ FactoryGirl.define do
end
factory :icecast_admin_authentication, :class => JamRuby::IcecastAdminAuthentication do
source_password Faker::Lorem.characters(10)
source_pass Faker::Lorem.characters(10)
admin_user Faker::Lorem.characters(10)
admin_password Faker::Lorem.characters(10)
admin_pass Faker::Lorem.characters(10)
relay_user Faker::Lorem.characters(10)
relay_password Faker::Lorem.characters(10)
relay_pass Faker::Lorem.characters(10)
end
factory :icecast_directory, :class => JamRuby::IcecastDirectory do
@ -168,8 +168,8 @@ FactoryGirl.define do
master_server_port 8000
master_update_interval 120
master_username Faker::Lorem.characters(10)
master_password Faker::Lorem.characters(10)
relays_on_demand true
master_pass Faker::Lorem.characters(10)
relays_on_demand 1
end
factory :icecast_path, :class => JamRuby::IcecastPath do
@ -189,24 +189,28 @@ FactoryGirl.define do
end
factory :icecast_security, :class => JamRuby::IcecastSecurity do
chroot false
chroot 0
end
factory :icecast_mount, :class => JamRuby::IcecastMount do
mount_name Faker::Lorem.characters(10)
username Faker::Lorem.characters(10)
password Faker::Lorem.characters(10)
name "/" + Faker::Lorem.characters(10)
source_username Faker::Lorem.characters(10)
source_pass Faker::Lorem.characters(10)
max_listeners 100
max_listener_duration 3600
fallback_mount Faker::Lorem.characters(10)
fallback_override true
fallback_when_full true
fallback_override 1
fallback_when_full 1
is_public -1
stream_name Faker::Lorem.characters(10)
stream_description Faker::Lorem.characters(10)
stream_url Faker::Lorem.characters(10)
genre Faker::Lorem.characters(10)
hidden false
hidden 0
factory :icecast_mount_with_auth do
association :authentication, :factory => :icecast_user_authentication
end
end
factory :icecast_listen_socket, :class => JamRuby::IcecastListenSocket do
@ -217,13 +221,13 @@ FactoryGirl.define do
port 8000
mount Faker::Lorem.characters(10)
server Faker::Lorem.characters(10)
on_demand true
on_demand 1
end
factory :icecast_user_authentication, :class => JamRuby::IcecastUserAuthentication do
authentication_type 'url'
username Faker::Lorem.characters(10)
password Faker::Lorem.characters(10)
unused_username Faker::Lorem.characters(10)
unused_pass Faker::Lorem.characters(10)
mount_add Faker::Lorem.characters(10)
mount_remove Faker::Lorem.characters(10)
listener_add Faker::Lorem.characters(10)
@ -233,18 +237,30 @@ FactoryGirl.define do
end
factory :icecast_server, :class => JamRuby::IcecastServer do
hostname Faker::Lorem.characters(10)
server_id Faker::Lorem.characters(10)
sequence(:hostname) { |n| "hostname-#{n}"}
sequence(:server_id) { |n| "server-#{n}"}
factory :icecast_server_minimal do
association :template, :factory => :icecast_template_minimal
factory :icecast_server_with_overrides do
association :limit, :factory => :icecast_limit
association :admin_auth, :factory => :icecast_admin_authentication
association :path, :factory => :icecast_path
association :logging, :factory => :icecast_logging
association :security, :factory => :icecast_security
before(:create) do |server|
server.listen_sockets << FactoryGirl.build(:icecast_listen_socket)
end
end
end
end
factory :icecast_template, :class => JamRuby::IcecastTemplate do
name Faker::Lorem.characters(10)
location Faker::Lorem.characters(10)
sequence(:name) { |n| "name-#{n}"}
sequence(:location) { |n| "location-#{n}"}
factory :icecast_template_minimal do
association :limit, :factory => :icecast_limit
@ -253,8 +269,8 @@ FactoryGirl.define do
association :logging, :factory => :icecast_logging
association :security, :factory => :icecast_security
before(:create) do |server|
server.listen_sockets << FactoryGirl.build(:icecast_listen_socket)
before(:create) do |template|
template.listen_sockets << FactoryGirl.build(:icecast_listen_socket)
end
end
end

View File

@ -8,19 +8,19 @@ describe IcecastAdminAuthentication do
it "save error" do
admin.save.should be_false
admin.errors[:source_password].length.should == 2
admin.errors[:source_pass].length.should == 2
admin.errors[:admin_user].length.should == 2
admin.errors[:admin_password].length.should == 2
admin.errors[:admin_pass].length.should == 2
admin.errors[:relay_user].length.should == 2
admin.errors[:relay_password].length.should == 2
admin.errors[:relay_pass].length.should == 2
end
it "save" do
admin.source_password = Faker::Lorem.characters(10)
admin.source_pass = Faker::Lorem.characters(10)
admin.admin_user = Faker::Lorem.characters(10)
admin.admin_password = Faker::Lorem.characters(10)
admin.admin_pass = Faker::Lorem.characters(10)
admin.relay_user = Faker::Lorem.characters(10)
admin.relay_password = Faker::Lorem.characters(10)
admin.relay_pass = Faker::Lorem.characters(10)
admin.save.should be_true
@ -29,16 +29,31 @@ describe IcecastAdminAuthentication do
output.rewind
xml = Nokogiri::XML(output)
xml.css('authentication source-password').text.should == admin.source_password
xml.css('authentication source-password').text.should == admin.source_pass
xml.css('authentication source-password').length.should == 1
xml.css('authentication admin-user').text.should == admin.admin_user
xml.css('authentication admin-user').length.should == 1
xml.css('authentication relay-user').text.should == admin.relay_user
xml.css('authentication relay-user').length.should == 1
xml.css('authentication relay-password').text.should == admin.relay_password
xml.css('authentication relay-password').text.should == admin.relay_pass
xml.css('authentication relay-password').length.should == 1
xml.css('authentication admin-password').text.should == admin.admin_password
xml.css('authentication admin-password').text.should == admin.admin_pass
xml.css('authentication admin-password').length.should == 1
end
describe "poke configs" do
let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) }
it "success via template" do
server.template.admin_auth.save!
server.reload
server.config_changed.should == 1
end
it "success via server" do
server.admin_auth.save!
server.reload
server.config_changed.should == 1
end
end
end

View File

@ -39,4 +39,29 @@ describe IcecastDirectory do
xml.css('directory yp-url').length.should == 1
end
describe "poke configs" do
let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) }
before(:each) do
server.directory = FactoryGirl.create(:icecast_directory)
server.template.directory = FactoryGirl.create(:icecast_directory)
server.template.save!
server.save!
server.config_updated
server.reload
end
it "success via template" do
server.template.directory.save!
server.reload
server.config_changed.should == 1
end
it "success via server" do
server.directory.save!
server.reload
server.config_changed.should == 1
end
end
end

View File

@ -56,4 +56,20 @@ describe IcecastLimit do
xml.css('limits burst-on-connect').text.should == "1"
xml.css('limits burst-size').text.should == limit.burst_size.to_s
end
describe "poke configs" do
let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) }
it "success via template" do
server.template.limit.save!
server.reload
server.config_changed.should == 1
end
it "success via server" do
server.limit.save!
server.reload
server.config_changed.should == 1
end
end
end

View File

@ -18,4 +18,19 @@ describe IcecastListenSocket do
xml.css('listen-socket bind-address').length.should == 0
end
describe "poke configs" do
let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) }
it "success via template" do
server.template.listen_sockets.first.save!
server.reload
server.config_changed.should == 1
end
it "success via server" do
server.listen_sockets.first.save!
server.reload
server.config_changed.should == 1
end
end
end

View File

@ -31,4 +31,19 @@ describe IcecastLogging do
xml.css('logging logarchive').length.should == 0
end
describe "poke configs" do
let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) }
it "success via template" do
server.template.logging.save!
server.reload
server.config_changed.should == 1
end
it "success via server" do
server.logging.save!
server.reload
server.config_changed.should == 1
end
end
end

View File

@ -9,7 +9,7 @@ describe IcecastMasterServerRelay do
it "should not save" do
relay.save.should be_false
relay.errors[:master_password].should == ["can't be blank", "is too short (minimum is 5 characters)"]
relay.errors[:master_pass].should == ["can't be blank", "is too short (minimum is 5 characters)"]
relay.errors[:master_username].should == ["can't be blank", "is too short (minimum is 5 characters)"]
relay.errors[:master_server].should == ["can't be blank", "is too short (minimum is 1 characters)"]
end
@ -18,8 +18,8 @@ describe IcecastMasterServerRelay do
relay.master_server = "test.www.com"
relay.master_server_port = 7111
relay.master_username = "hackme-user"
relay.master_password = "hackme-password"
relay.save.should be_true
relay.master_pass = "hackme-password"
relay.save!
root.tag! 'root' do |builder|
relay.dumpXml(builder)
@ -31,8 +31,33 @@ describe IcecastMasterServerRelay do
xml.css('root master-server-port').text.should == relay.master_server_port.to_s
xml.css('root master-update-interval').text.should == relay.master_update_interval.to_s
xml.css('root master-username').text.should == relay.master_username.to_s
xml.css('root master-password').text.should == relay.master_password.to_s
xml.css('root relays-on-demand').text.should == (relay.relays_on_demand ? '1' : '0')
xml.css('root master-password').text.should == relay.master_pass.to_s
xml.css('root relays-on-demand').text.should == relay.relays_on_demand.to_s
end
describe "poke configs" do
let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) }
before(:each) do
server.master_relay = FactoryGirl.create(:icecast_master_server_relay)
server.template.master_relay = FactoryGirl.create(:icecast_master_server_relay)
server.template.save!
server.save!
server.config_updated
server.reload
end
it "success via template" do
server.template.master_relay.save!
server.reload
server.config_changed.should == 1
end
it "success via server" do
server.master_relay.save!
server.reload
server.config_changed.should == 1
end
end
end

View File

@ -2,13 +2,14 @@ require 'spec_helper'
describe IcecastMount do
let(:mount) { IcecastMount.new }
let(:icecast_mount) { FactoryGirl.create(:icecast_mount) }
let(:output) { StringIO.new }
let(:builder) { ::Builder::XmlMarkup.new(:target => output, :indent => 1) }
it "save error" do
mount = IcecastMount.new
mount.save.should be_false
mount.errors[:mount_name].should == ["can't be blank"]
mount.errors[:name].should == ["can't be blank", "must start with /"]
mount.errors[:stream_name].should == ["can't be blank"]
mount.errors[:stream_description].should == ["can't be blank"]
mount.errors[:stream_url].should == ["can't be blank"]
@ -17,13 +18,14 @@ describe IcecastMount do
it "save" do
mount.mount_name = Faker::Lorem.characters(10)
mount = IcecastMount.new
mount.name = "/" + Faker::Lorem.characters(10)
mount.stream_name = Faker::Lorem.characters(10)
mount.stream_description = Faker::Lorem.characters(10)
mount.stream_url = Faker::Lorem.characters(10)
mount.genre = Faker::Lorem.characters(10)
mount.username = Faker::Lorem.characters(10)
mount.password = Faker::Lorem.characters(10)
mount.source_username = Faker::Lorem.characters(10)
mount.source_pass = Faker::Lorem.characters(10)
mount.intro = Faker::Lorem.characters(10)
mount.fallback_mount = Faker::Lorem.characters(10)
mount.on_connect = Faker::Lorem.characters(10)
@ -41,15 +43,15 @@ describe IcecastMount do
output.rewind
xml = Nokogiri::XML(output)
xml.css('mount mount-name').text.should == mount.mount_name
xml.css('mount username').text.should == mount.username
xml.css('mount password').text.should == mount.password
xml.css('mount mount-name').text.should == mount.name
xml.css('mount username').text.should == mount.source_username
xml.css('mount password').text.should == mount.source_pass
xml.css('mount max-listeners').text.should == mount.max_listeners.to_s
xml.css('mount max-listener-duration').text.should == mount.max_listener_duration.to_s
xml.css('mount intro').text.should == mount.intro
xml.css('mount fallback-mount').text.should == mount.fallback_mount
xml.css('mount fallback-override').text.should == (mount.fallback_override ? '1' : '0')
xml.css('mount fallback-when-full').text.should == (mount.fallback_when_full ? '1' : '0')
xml.css('mount fallback-override').text.should == mount.fallback_override.to_s
xml.css('mount fallback-when-full').text.should == mount.fallback_when_full.to_s
xml.css('mount stream-name').text.should == mount.stream_name
xml.css('mount stream-description').text.should == mount.stream_description
xml.css('mount stream-url').text.should == mount.stream_url
@ -61,12 +63,34 @@ describe IcecastMount do
xml.css('mount subtype').text == mount.subtype
xml.css('mount burst-size').length.should == 0
xml.css('mount mp3-metadata-interval').length.should == 0
xml.css('mount hidden').text.should == (mount.hidden ? '1' : '0')
xml.css('mount hidden').text.should == mount.hidden.to_s
xml.css('mount on-connect').text.should == mount.on_connect
xml.css('mount on-disconnect').text.should == mount.on_disconnect
xml.css('mount dump-file').length.should == 0
xml.css('mount authentication').length.should == 1 # no reason to test futher; it's tested in that model
end
describe "poke configs" do
let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) }
before(:each) do
server.mounts << FactoryGirl.create(:icecast_mount)
server.save!
server.config_updated
server.reload
server.config_changed.should == 0
end
it "success via server" do
server.mounts.first.save!
server.reload
server.config_changed.should == 1
end
end
describe "icecast server callbacks" do
it "source up" do
icecast_mount.source_up
end
end
end

View File

@ -54,4 +54,20 @@ describe IcecastPath do
xml.css('paths alias').first['source'] == path.alias_source
xml.css('paths alias').first['dest'] == path.alias_dest
end
describe "poke configs" do
let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) }
it "success via template" do
server.template.path.save!
server.reload
server.config_changed.should == 1
end
it "success via server" do
server.path.save!
server.reload
server.config_changed.should == 1
end
end
end

View File

@ -33,4 +33,20 @@ describe IcecastRelay do
xml.css('relay on-demand').text.should == "1"
end
describe "poke configs" do
let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) }
before(:each) do
server.relays << FactoryGirl.create(:icecast_relay)
server.save!
server.config_updated
server.reload
end
it "success via server" do
server.relays.first.save!
server.reload
server.config_changed.should == 1
end
end
end

View File

@ -9,7 +9,7 @@ describe IcecastSecurity do
it "save with chroot" do
security.change_owner_user ="hotdog"
security.change_owner_group ="mongrel"
security.chroot = true
security.chroot = 1
security.save!
security.dumpXml(builder)
@ -30,4 +30,20 @@ describe IcecastSecurity do
xml.css('security chroot').text.should == '0'
xml.css('security changeowner').length.should == 0
end
describe "poke configs" do
let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) }
it "success via template" do
server.template.security.save!
server.reload
server.config_changed.should == 1
end
it "success via server" do
server.security.save!
server.reload
server.config_changed.should == 1
end
end
end

View File

@ -11,15 +11,16 @@ describe IcecastServer do
server = FactoryGirl.create(:icecast_server_minimal)
server.save!
server.reload
server.dumpXml(builder)
server.dumpXml(output)
output.rewind
xml = Nokogiri::XML(output)
xml.css('icecast hostname').text.should == server.hostname
xml.css('icecast server-id').text.should == server.server_id
xml.css('icecast location').text.should == server.template.location
xml.css('icecast admin').text.should == server.template.admin_email
xml.css('icecast fileserve').text.should == (server.template.fileserve ? '1' : '0')
xml.css('icecast fileserve').text.should == server.template.fileserve.to_s
xml.css('icecast limits').length.should == 1
xml.css('icecast authentication').length.should == 1
xml.css('icecast directory').length.should == 0

View File

@ -12,8 +12,8 @@ describe IcecastUserAuthentication do
auth.errors[:mount_remove].should == ["can't be blank"]
auth.errors[:listener_add].should == ["can't be blank"]
auth.errors[:listener_remove].should == ["can't be blank"]
auth.errors[:username].should == ["is too short (minimum is 5 characters)"]
auth.errors[:password].should == ["is too short (minimum is 5 characters)"]
#auth.errors[:unused_username].should == ["is too short (minimum is 5 characters)"]
#auth.errors[:unused_pass].should == ["is too short (minimum is 5 characters)"]
end
it "save" do
@ -21,8 +21,8 @@ describe IcecastUserAuthentication do
auth.mount_remove = Faker::Lorem.characters(10)
auth.listener_add = Faker::Lorem.characters(10)
auth.listener_remove = Faker::Lorem.characters(10)
auth.username = Faker::Lorem.characters(10)
auth.password = Faker::Lorem.characters(10)
auth.unused_username = Faker::Lorem.characters(10)
auth.unused_pass = Faker::Lorem.characters(10)
auth.save!
@ -36,12 +36,26 @@ describe IcecastUserAuthentication do
xml.css('authentication option[name="mount_remove"]')[0]["value"].should == auth.mount_remove
xml.css('authentication option[name="listener_add"]')[0]["value"].should == auth.listener_add
xml.css('authentication option[name="listener_remove"]')[0]["value"].should == auth.listener_remove
xml.css('authentication option[name="username"]')[0]["value"].should == auth.username
xml.css('authentication option[name="username"]')[0]["value"].should == auth.unused_username
xml.css('authentication option[name="password"]')[0]["value"].should == auth.unused_pass
xml.css('authentication option[name="auth_header"]')[0]["value"].should == auth.auth_header
xml.css('authentication option[name="timelimit_header"]')[0]["value"].should == auth.timelimit_header
end
describe "poke configs" do
let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) }
before(:each) do
server.mounts << FactoryGirl.create(:icecast_mount_with_auth)
server.save!
server.config_updated
server.reload
end
it "success via server" do
server.mounts.first.authentication.save!
server.reload
server.config_changed.should == 1
end
end
end

View File

@ -21,39 +21,52 @@ describe MQRouter do
@mq_router.user_publish_to_session(music_session, user1, "a message" ,:client_id => music_session_member1.client_id)
end
it "user_publish_to_session works (checking exchange callbacks)" do
user1 = FactoryGirl.create(:user) # in the jam session
user2 = FactoryGirl.create(:user) # in the jam session
music_session = FactoryGirl.create(:music_session, :creator => user1)
music_session_member1 = FactoryGirl.create(:connection, :user => user1, :music_session => music_session, :ip_address => "1.1.1.1", :client_id => "1")
music_session_member2 = FactoryGirl.create(:connection, :user => user2, :music_session => music_session, :ip_address => "2.2.2.2", :client_id => "2")
# this is necessary because other tests will call EM.schedule indirectly as they fiddle with AR models, since some of our
# notifications are tied to model activity. So, the issue here is that you'll have an unknown known amount of
# queued up messages ready to be sent to MQRouter (because EM.schedule will put deferred blocks onto @next_tick_queue),
# resulting in messages from other tests being sent to client_exchange or user_exchange
# there is no API I can see to clear out the EM queue. so just open up the EM module and do it manually
module EM
@next_tick_queue = []
describe "double MQRouter" do
before(:all) do
@original_client_exchange = MQRouter.client_exchange
@original_user_exchange = MQRouter.user_exchange
end
EM.run do
after(:all) do
MQRouter.client_exchange = @original_client_exchange
MQRouter.user_exchange = @original_user_exchange
end
# mock up exchange
MQRouter.client_exchange = double("client_exchange")
MQRouter.user_exchange = double("user_exchange")
it "user_publish_to_session works (checking exchange callbacks)" do
MQRouter.client_exchange.should_receive(:publish).with("a message", :routing_key => "client.#{music_session_member2.client_id}")
MQRouter.user_exchange.should_not_receive(:publish)
user1 = FactoryGirl.create(:user) # in the jam session
user2 = FactoryGirl.create(:user) # in the jam session
@mq_router.user_publish_to_session(music_session, user1, "a message", :client_id => music_session_member1.client_id)
music_session = FactoryGirl.create(:music_session, :creator => user1)
EM.stop
music_session_member1 = FactoryGirl.create(:connection, :user => user1, :music_session => music_session, :ip_address => "1.1.1.1", :client_id => "1")
music_session_member2 = FactoryGirl.create(:connection, :user => user2, :music_session => music_session, :ip_address => "2.2.2.2", :client_id => "2")
# this is necessary because other tests will call EM.schedule indirectly as they fiddle with AR models, since some of our
# notifications are tied to model activity. So, the issue here is that you'll have an unknown known amount of
# queued up messages ready to be sent to MQRouter (because EM.schedule will put deferred blocks onto @next_tick_queue),
# resulting in messages from other tests being sent to client_exchange or user_exchange
# there is no API I can see to clear out the EM queue. so just open up the EM module and do it manually
module EM
@next_tick_queue = []
end
# bad thing about a static singleton is that we have to 'repair' it by taking back off the double
EM.run do
# mock up exchange
MQRouter.client_exchange = double("client_exchange")
MQRouter.user_exchange = double("user_exchange")
MQRouter.client_exchange.should_receive(:publish).with("a message", :routing_key => "client.#{music_session_member2.client_id}")
MQRouter.user_exchange.should_not_receive(:publish)
@mq_router.user_publish_to_session(music_session, user1, "a message", :client_id => music_session_member1.client_id)
EM.stop
end
end
end

View File

@ -38,6 +38,14 @@ describe AudioMixer do
before(:each) do
stub_const("APP_CONFIG", app_config)
module EM
@next_tick_queue = []
end
MQRouter.client_exchange = double("client_exchange")
MQRouter.user_exchange = double("user_exchange")
MQRouter.client_exchange.should_receive(:publish).any_number_of_times
MQRouter.user_exchange.should_receive(:publish).any_number_of_times
end

View File

@ -0,0 +1,155 @@
require 'spec_helper'
require 'fileutils'
# these tests avoid the use of ActiveRecord and FactoryGirl to do blackbox, non test-instrumented tests
describe IcecastConfigWriter do
let(:worker) { IcecastConfigWriter.new }
describe "validate" do
it "no manifest" do
expect { worker.validate }.to raise_error("icecast_server_id not spceified")
end
it "no files specified" do
worker.icecast_server_id = 'something'
expect { worker.validate }.to raise_error("queue routing mismatch error")
end
it "succeeds" do
worker.icecast_server_id = APP_CONFIG.icecast_server_id
worker.validate
end
end
describe "reload" do
it "works with successful command" do
IcecastConfigWriter.any_instance.stub(:execute).and_return(0)
worker.reload
end
it "raise exception when command fails" do
IcecastConfigWriter.any_instance.stub(:execute).and_return(1)
expect { worker.reload }.to raise_error
end
end
describe "integration" do
let(:server) {FactoryGirl.create(:icecast_server_minimal, server_id: APP_CONFIG.icecast_server_id)}
describe "simulated" do
describe "perform" do
# this case does not talk to redis, does not run a real reload command.
# but it does talk to the database and verifies all the other logic
it "success" do
# return success code from reload command
IcecastConfigWriter.any_instance.stub(:execute).and_return(0)
server.location = 'hello'
server.save!
server.config_changed.should == 1
IcecastConfigWriter.perform(server.server_id)
server.reload
server.config_changed.should == 0
end
it "errored" do
# return error code from reload command, which will cause the job to blow up
IcecastConfigWriter.any_instance.stub(:execute).and_return(1)
server.save!
server.config_changed.should == 1
expect { IcecastConfigWriter.perform(server.server_id) }.to raise_error
server.reload
server.config_changed.should == 1
end
end
describe "with resque-spec" do
before(:each) do
ResqueSpec.reset!
end
it "should have been enqueued because the config changed" do
server.save!
# the act of just creating the IcecastServer puts a message on the queue
IcecastConfigWriter.should have_queue_size_of(1)
end
it "should not have been enqueued if routed to a different server_id" do
new_server = FactoryGirl.create(:icecast_server_minimal, server_id: APP_CONFIG.icecast_server_id)
with_resque do
new_server.save!
end
# nobody was around to take it from the queue
IcecastConfigWriter.should have_queue_size_of(1)
end
it "should actually run the job" do
IcecastConfigWriter.any_instance.stub(:execute).and_return(0)
with_resque do
server.save!
server.config_changed.should == 1
end
IcecastConfigWriter.should have_queue_size_of(0)
server.reload
server.config_changed.should == 0
end
it "bails out with no error if no config change present" do
IcecastConfigWriter.any_instance.stub(:execute).and_return(0)
with_resque do
server.save!
end
server.reload
server.config_changed.should == 0
with_resque do
IcecastConfigWriter.enqueue(server.server_id)
end
server.reload
server.config_changed.should == 0
end
describe "queue_jobs_needing_retry" do
it "finds an unchecked server" do
server.touch
begin
ActiveRecord::Base.record_timestamps = false
server.updated_at = Time.now.ago(APP_CONFIG.icecast_max_missing_check + 1)
server.save!
ensure
# very important to turn it back; it'll break all tests otherwise
ActiveRecord::Base.record_timestamps = true
end
# should enqueue 1 job
IcecastConfigWriter.queue_jobs_needing_retry
IcecastConfigWriter.should have_queue_size_of(1)
end
it "does not find a recently checked server" do
# should enqueue 1 job
IcecastConfigWriter.queue_jobs_needing_retry
IcecastConfigWriter.should have_queue_size_of(0)
end
end
end
end
end
end

View File

@ -84,6 +84,7 @@ Spork.prefork do
end
config.before(:each) do
stub_const("APP_CONFIG", app_config)
DatabaseCleaner.start
end

View File

@ -21,6 +21,22 @@ def app_config
ENV['AUDIOMIXER_PATH'] || audiomixer_workspace_path || "/var/lib/audiomixer/audiomixer/audiomixerapp"
end
def icecast_reload_cmd
'true' # as in, /bin/true
end
def icecast_config_file
Dir::Tmpname.make_tmpname(["#{Dir.tmpdir}/icecast", 'xml'], nil)
end
def icecast_server_id
'test'
end
def icecast_max_missing_check
2 * 60 # 2 minutes
end
def rabbitmq_host
"localhost"
end
@ -29,6 +45,7 @@ def app_config
5672
end
private
def audiomixer_workspace_path

View File

@ -0,0 +1,73 @@
class ApiIcecastController < ApiController
before_filter :local_only
before_filter :parse_mount
# each request will have this in it, if it's icecast.
#user-agent = Icecast 2.3.3
def mount_add
mount = IcecastMount.find(@mount_id)
mount.source_up
render text: '', :status => :ok
end
def mount_remove
mount = IcecastMount.find(@mount_id)
mount.source_down
render text: '', :status => :ok
end
def listener_add
client = params[:client] # icecast internal id, e.g. 149
user = params[:user] # basic auth in the request sent to icecast
pass = params[:pass] # basic auth in the request sent to icecast
remote_ip = params[:ip]
remote_user_agent = params[:agent]
mount = IcecastMount.find(@mount_id)
mount.listener_add
render text: '', :status => :ok
end
def listener_remove
client = params[:client] # you can use this to correlate the listener_add...
user = params[:user] # or user/pass (icecast is storing these as well and reflects them back)
pass = params[:pass]
duration = params[:duration] # seconds connected to the listen stream
mount = IcecastMount.find(@mount_id)
mount.listener_remove
render text: '', :status => :ok
end
protected
def local_only
request.local?
end
def parse_mount()
mount = params[:mount]
# Example A
# browser: http://icecast/a
# mount: /a
#
# Example B
# browser: http://icecast/a?dog=legs&mump
# mount: /a?dog=legs&mump
# browser: http://icecast/a#bleh
# mount: /a
uri = URI(mount)
@mount_id = uri.path
@mount_params = Rack::Utils.parse_query(uri.query)
end
end

View File

@ -6,6 +6,8 @@ require "action_controller/railtie"
require "action_mailer/railtie"
require "active_resource/railtie"
require "sprockets/railtie"
require 'shellwords'
# initialize ActiveRecord's db connection
@ -169,11 +171,14 @@ include JamRuby
config.audiomixer_path = "/var/lib/audiomixer/audiomixer/audiomixerapp"
# if it looks like linux, use init.d script; otherwise use kill
config.icecast_reload_cmd = File.exist? '/usr/bin/icecast2' ? '/etc/init.d/icecast2 reload' : 'bash -l -c "kill -1 `ps -f -c | grep icecast | awk \'{print $2}\'`"'
config.icecast_reload_cmd = ENV['ICECAST_RELOAD_CMD'] || (File.exist?('/usr/bin/icecast2') ? '/etc/init.d/icecast2 reload' : "bash -l -c #{Shellwords.escape("kill -1 `ps -f | grep /usr/local/bin/icecast | grep -v grep | awk \'{print $2}\'`")}")
# if it looks like linux, use that path; otherwise use the brew default path
config.icecast_config_file = File.exist? '/etc/icecast2/icecast.xml' ? '/etc/icecast2/icecast.xml' : '/usr/local/etc/icecast.xml'
config.icecast_config_file = ENV['ICECAST_CONFIG_FILE'] || (File.exist?('/etc/icecast2/icecast.xml') ? '/etc/icecast2/icecast.xml' : '/usr/local/etc/icecast.xml')
# this will be the qualifier on the IcecastConfigWorker queue name
config.icecast_server_id = ENV['ICECAST_SERVER_ID'] || 'localhost'
config.icecast_max_missing_check = 2 * 60 # 2 minutes
config.email_alerts_alias = 'alerts@jamkazam.com' # should be used for 'oh no' server down/service down sorts of emails
config.email_alerts_alias = 'nobody@jamkazam.com' # should be used for 'oh no' server down/service down sorts of emails
config.email_generic_from = 'nobody@jamkazam.com'
config.email_smtp_address = 'smtp.sendgrid.net'
config.email_smtp_port = 587

View File

@ -1,6 +1,6 @@
require 'resque_failed_job_mailer'
Resque::Failure::Notifier.configure do |config|
config.from = Rails.application.config.email_alerts_alias
config.to = Rails.application.config.email_generic_from
config.to = Rails.application.config.email_alerts_alias
config.from = Rails.application.config.email_generic_from
end

View File

@ -312,6 +312,10 @@ SampleApp::Application.routes.draw do
# feedback from corporate site api
match '/feedback' => 'api_corporate#feedback', :via => :post
# icecast urls
match '/icecast/mount_add' => 'api_icecast#mount_add', :via => :post
match '/icecast/mount_remove' => 'api_icecast#mount_remove', :via => :post
match '/icecast/listener_add' => 'api_icecast#listener_add', :via => :post
match '/icecast/listener_remove' => 'api_icecast#listener_remove', :via => :post
end
end

View File

@ -2,4 +2,9 @@
AudioMixerRetry:
cron: 0 * * * *
class: "JamRuby::AudioMixerRetry"
description: "Retries mixes that set the should_retry flag or never started"
description: "Retries mixes that set the should_retry flag or never started"
IcecastConfigRetry:
cron: 0 * * * *
class: "JamRuby::IcecastConfigRetry"
description: "Finds icecast servers that have had their config_changed, but no IcecastConfigWriter check recently"