diff --git a/admin/app/admin/icecast_bootstrap.rb b/admin/app/admin/icecast_bootstrap.rb index 201575fb4..1cc15e22e 100644 --- a/admin/app/admin/icecast_bootstrap.rb +++ b/admin/app/admin/icecast_bootstrap.rb @@ -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 diff --git a/admin/app/admin/icecast_server_mount.rb b/admin/app/admin/icecast_server_mount.rb new file mode 100644 index 000000000..2b6badd9b --- /dev/null +++ b/admin/app/admin/icecast_server_mount.rb @@ -0,0 +1,4 @@ +ActiveAdmin.register JamRuby::IcecastServerMount, :as => 'ServerMounts' do + menu :parent => 'Icecast' + +end diff --git a/admin/app/admin/icecast_server_relay.rb b/admin/app/admin/icecast_server_relay.rb new file mode 100644 index 000000000..d3f0e8b5b --- /dev/null +++ b/admin/app/admin/icecast_server_relay.rb @@ -0,0 +1,3 @@ +ActiveAdmin.register JamRuby::IcecastServerRelay, :as => 'ServerRelays' do + menu :parent => 'Icecast' +end diff --git a/admin/app/admin/icecast_server_socket.rb b/admin/app/admin/icecast_server_socket.rb new file mode 100644 index 000000000..70479b991 --- /dev/null +++ b/admin/app/admin/icecast_server_socket.rb @@ -0,0 +1,3 @@ +ActiveAdmin.register JamRuby::IcecastServerSocket, :as => 'ServerListenSockets' do + menu :parent => 'Icecast' +end diff --git a/admin/app/admin/icecast_template_socket.rb b/admin/app/admin/icecast_template_socket.rb new file mode 100644 index 000000000..beb5c96a5 --- /dev/null +++ b/admin/app/admin/icecast_template_socket.rb @@ -0,0 +1,3 @@ +ActiveAdmin.register JamRuby::IcecastTemplateSocket, :as => 'TemplateListenSockets' do + menu :parent => 'Icecast' +end diff --git a/admin/config/initializers/active_admin.rb b/admin/config/initializers/active_admin.rb index ab2394c35..2db685656 100644 --- a/admin/config/initializers/active_admin.rb +++ b/admin/config/initializers/active_admin.rb @@ -6,6 +6,11 @@ class Footer < ActiveAdmin::Component end end +module ActiveAdmin + class BaseController + with_role :admin + end +end ActiveAdmin.setup do |config| diff --git a/db/build b/db/build index e0ece07a9..259aea93b 100755 --- a/db/build +++ b/db/build @@ -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 diff --git a/db/up/icecast.sql b/db/up/icecast.sql index 413995310..430ca0d14 100644 --- a/db/up/icecast.sql +++ b/db/up/icecast.sql @@ -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 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. diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto index d39447312..6dff2d5cd 100644 --- a/pb/src/client_container.proto +++ b/pb/src/client_container.proto @@ -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 { diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index e683d8088..9f5eb11eb 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/icecast_admin_authentication.rb b/ruby/lib/jam_ruby/models/icecast_admin_authentication.rb index e09f0bf6a..4880495b5 100644 --- a/ruby/lib/jam_ruby/models/icecast_admin_authentication.rb +++ b/ruby/lib/jam_ruby/models/icecast_admin_authentication.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/icecast_directory.rb b/ruby/lib/jam_ruby/models/icecast_directory.rb index f308f147a..29ddd2b6a 100644 --- a/ruby/lib/jam_ruby/models/icecast_directory.rb +++ b/ruby/lib/jam_ruby/models/icecast_directory.rb @@ -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| diff --git a/ruby/lib/jam_ruby/models/icecast_limit.rb b/ruby/lib/jam_ruby/models/icecast_limit.rb index 26375768f..76a95ee3e 100644 --- a/ruby/lib/jam_ruby/models/icecast_limit.rb +++ b/ruby/lib/jam_ruby/models/icecast_limit.rb @@ -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| diff --git a/ruby/lib/jam_ruby/models/icecast_listen_socket.rb b/ruby/lib/jam_ruby/models/icecast_listen_socket.rb index eef194d34..569740eb4 100644 --- a/ruby/lib/jam_ruby/models/icecast_listen_socket.rb +++ b/ruby/lib/jam_ruby/models/icecast_listen_socket.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/icecast_logging.rb b/ruby/lib/jam_ruby/models/icecast_logging.rb index 3afcdcd9a..0d41bd0e1 100644 --- a/ruby/lib/jam_ruby/models/icecast_logging.rb +++ b/ruby/lib/jam_ruby/models/icecast_logging.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/icecast_master_server_relay.rb b/ruby/lib/jam_ruby/models/icecast_master_server_relay.rb index 6c678a9fa..0c3d04733 100644 --- a/ruby/lib/jam_ruby/models/icecast_master_server_relay.rb +++ b/ruby/lib/jam_ruby/models/icecast_master_server_relay.rb @@ -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 \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/icecast_mount.rb b/ruby/lib/jam_ruby/models/icecast_mount.rb index 5b938707e..2b19cee51 100644 --- a/ruby/lib/jam_ruby/models/icecast_mount.rb +++ b/ruby/lib/jam_ruby/models/icecast_mount.rb @@ -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 \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/icecast_path.rb b/ruby/lib/jam_ruby/models/icecast_path.rb index b84022535..ab74fe45a 100644 --- a/ruby/lib/jam_ruby/models/icecast_path.rb +++ b/ruby/lib/jam_ruby/models/icecast_path.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/icecast_relay.rb b/ruby/lib/jam_ruby/models/icecast_relay.rb index 978997a06..f8c6bc712 100644 --- a/ruby/lib/jam_ruby/models/icecast_relay.rb +++ b/ruby/lib/jam_ruby/models/icecast_relay.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/icecast_security.rb b/ruby/lib/jam_ruby/models/icecast_security.rb index d6bfc45a7..2980f72e4 100644 --- a/ruby/lib/jam_ruby/models/icecast_security.rb +++ b/ruby/lib/jam_ruby/models/icecast_security.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/icecast_server.rb b/ruby/lib/jam_ruby/models/icecast_server.rb index a4961fa51..39320925b 100644 --- a/ruby/lib/jam_ruby/models/icecast_server.rb +++ b/ruby/lib/jam_ruby/models/icecast_server.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/icecast_server_mount.rb b/ruby/lib/jam_ruby/models/icecast_server_mount.rb index ec48dd2ea..2eb73abe9 100644 --- a/ruby/lib/jam_ruby/models/icecast_server_mount.rb +++ b/ruby/lib/jam_ruby/models/icecast_server_mount.rb @@ -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 \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/icecast_server_relay.rb b/ruby/lib/jam_ruby/models/icecast_server_relay.rb index a80bd8dde..cde8a4df0 100644 --- a/ruby/lib/jam_ruby/models/icecast_server_relay.rb +++ b/ruby/lib/jam_ruby/models/icecast_server_relay.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/icecast_server_socket.rb b/ruby/lib/jam_ruby/models/icecast_server_socket.rb index dde090f3c..b99d5a006 100644 --- a/ruby/lib/jam_ruby/models/icecast_server_socket.rb +++ b/ruby/lib/jam_ruby/models/icecast_server_socket.rb @@ -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 \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/icecast_template.rb b/ruby/lib/jam_ruby/models/icecast_template.rb index 96226f268..6813c2a04 100644 --- a/ruby/lib/jam_ruby/models/icecast_template.rb +++ b/ruby/lib/jam_ruby/models/icecast_template.rb @@ -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 \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/icecast_template_socket.rb b/ruby/lib/jam_ruby/models/icecast_template_socket.rb new file mode 100644 index 000000000..91c0f2eed --- /dev/null +++ b/ruby/lib/jam_ruby/models/icecast_template_socket.rb @@ -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 \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/icecast_user_authentication.rb b/ruby/lib/jam_ruby/models/icecast_user_authentication.rb index 538d82efd..01da5b32c 100644 --- a/ruby/lib/jam_ruby/models/icecast_user_authentication.rb +++ b/ruby/lib/jam_ruby/models/icecast_user_authentication.rb @@ -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 \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/mix.rb b/ruby/lib/jam_ruby/models/mix.rb index cf96fb8f7..66a58e1e5 100644 --- a/ruby/lib/jam_ruby/models/mix.rb +++ b/ruby/lib/jam_ruby/models/mix.rb @@ -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 diff --git a/ruby/lib/jam_ruby/resque/audiomixer.rb b/ruby/lib/jam_ruby/resque/audiomixer.rb index e1bde1947..3424b11d0 100644 --- a/ruby/lib/jam_ruby/resque/audiomixer.rb +++ b/ruby/lib/jam_ruby/resque/audiomixer.rb @@ -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 diff --git a/ruby/lib/jam_ruby/resque/icecast_config_writer.rb b/ruby/lib/jam_ruby/resque/icecast_config_writer.rb index 6c9964e76..b86c9ead7 100644 --- a/ruby/lib/jam_ruby/resque/icecast_config_writer.rb +++ b/ruby/lib/jam_ruby/resque/icecast_config_writer.rb @@ -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 \ No newline at end of file diff --git a/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb b/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb index 855bdd747..34e7571cb 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb @@ -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 diff --git a/ruby/lib/jam_ruby/resque/scheduled/icecast_config_retry.rb b/ruby/lib/jam_ruby/resque/scheduled/icecast_config_retry.rb new file mode 100644 index 000000000..f0388d8f5 --- /dev/null +++ b/ruby/lib/jam_ruby/resque/scheduled/icecast_config_retry.rb @@ -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 \ No newline at end of file diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 1cdaede1b..4a51defe2 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -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 diff --git a/ruby/spec/jam_ruby/models/icecast_admin_authentication_spec.rb b/ruby/spec/jam_ruby/models/icecast_admin_authentication_spec.rb index dfde74450..48ec8f43e 100644 --- a/ruby/spec/jam_ruby/models/icecast_admin_authentication_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_admin_authentication_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/models/icecast_directory_spec.rb b/ruby/spec/jam_ruby/models/icecast_directory_spec.rb index 6e8622a76..203cead6a 100644 --- a/ruby/spec/jam_ruby/models/icecast_directory_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_directory_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/models/icecast_limit_spec.rb b/ruby/spec/jam_ruby/models/icecast_limit_spec.rb index 2798d766d..910b2fca4 100644 --- a/ruby/spec/jam_ruby/models/icecast_limit_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_limit_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/models/icecast_listen_socket_spec.rb b/ruby/spec/jam_ruby/models/icecast_listen_socket_spec.rb index 161a103c5..1de0009a0 100644 --- a/ruby/spec/jam_ruby/models/icecast_listen_socket_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_listen_socket_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/models/icecast_logging_spec.rb b/ruby/spec/jam_ruby/models/icecast_logging_spec.rb index d4fa47f73..2ad726bcb 100644 --- a/ruby/spec/jam_ruby/models/icecast_logging_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_logging_spec.rb @@ -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 \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/icecast_master_server_relay_spec.rb b/ruby/spec/jam_ruby/models/icecast_master_server_relay_spec.rb index 132f2c6d4..5de60553e 100644 --- a/ruby/spec/jam_ruby/models/icecast_master_server_relay_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_master_server_relay_spec.rb @@ -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 \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/icecast_mount_spec.rb b/ruby/spec/jam_ruby/models/icecast_mount_spec.rb index 7c38c2589..5a42a0397 100644 --- a/ruby/spec/jam_ruby/models/icecast_mount_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_mount_spec.rb @@ -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 \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/icecast_path_spec.rb b/ruby/spec/jam_ruby/models/icecast_path_spec.rb index c7b4c6853..0f73acd09 100644 --- a/ruby/spec/jam_ruby/models/icecast_path_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_path_spec.rb @@ -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 \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/icecast_relay_spec.rb b/ruby/spec/jam_ruby/models/icecast_relay_spec.rb index 8cc3f9a00..09c46e303 100644 --- a/ruby/spec/jam_ruby/models/icecast_relay_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_relay_spec.rb @@ -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 \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/icecast_security_spec.rb b/ruby/spec/jam_ruby/models/icecast_security_spec.rb index a0704b12a..08ad498a9 100644 --- a/ruby/spec/jam_ruby/models/icecast_security_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_security_spec.rb @@ -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 \ No newline at end of file diff --git a/ruby/spec/jam_ruby/models/icecast_server_spec.rb b/ruby/spec/jam_ruby/models/icecast_server_spec.rb index f667c6994..a7831aa72 100644 --- a/ruby/spec/jam_ruby/models/icecast_server_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_server_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/models/icecast_servermisc_spec.rb b/ruby/spec/jam_ruby/models/icecast_servermisc_spec.rb deleted file mode 100644 index e69de29bb..000000000 diff --git a/ruby/spec/jam_ruby/models/icecast_user_authentication_spec.rb b/ruby/spec/jam_ruby/models/icecast_user_authentication_spec.rb index 7ae99512a..8e72506c9 100644 --- a/ruby/spec/jam_ruby/models/icecast_user_authentication_spec.rb +++ b/ruby/spec/jam_ruby/models/icecast_user_authentication_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/mq_router_spec.rb b/ruby/spec/jam_ruby/mq_router_spec.rb index 4de2f124c..b9224bde2 100644 --- a/ruby/spec/jam_ruby/mq_router_spec.rb +++ b/ruby/spec/jam_ruby/mq_router_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/resque/audiomixer_spec.rb b/ruby/spec/jam_ruby/resque/audiomixer_spec.rb index 8d62c373d..1535717f7 100644 --- a/ruby/spec/jam_ruby/resque/audiomixer_spec.rb +++ b/ruby/spec/jam_ruby/resque/audiomixer_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/resque/icecast_config_worker_spec.rb b/ruby/spec/jam_ruby/resque/icecast_config_worker_spec.rb new file mode 100644 index 000000000..4eb95adcf --- /dev/null +++ b/ruby/spec/jam_ruby/resque/icecast_config_worker_spec.rb @@ -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 diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index 2432e9f7f..2fd679cc5 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -84,6 +84,7 @@ Spork.prefork do end config.before(:each) do + stub_const("APP_CONFIG", app_config) DatabaseCleaner.start end diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb index 9ef5410ed..84b51f440 100644 --- a/ruby/spec/support/utilities.rb +++ b/ruby/spec/support/utilities.rb @@ -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 diff --git a/web/app/controllers/api_icecast_controller.rb b/web/app/controllers/api_icecast_controller.rb new file mode 100644 index 000000000..2d0986705 --- /dev/null +++ b/web/app/controllers/api_icecast_controller.rb @@ -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 diff --git a/web/config/application.rb b/web/config/application.rb index d8cdbd50a..fc11d17b8 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -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 diff --git a/web/config/initializers/resque_failed_job_mailer.rb b/web/config/initializers/resque_failed_job_mailer.rb index 70a7ab2c7..446c306ea 100644 --- a/web/config/initializers/resque_failed_job_mailer.rb +++ b/web/config/initializers/resque_failed_job_mailer.rb @@ -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 \ No newline at end of file diff --git a/web/config/routes.rb b/web/config/routes.rb index 990ca7426..510df80f5 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -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 diff --git a/web/config/scheduler.yml b/web/config/scheduler.yml index 815996110..97adc2d3c 100644 --- a/web/config/scheduler.yml +++ b/web/config/scheduler.yml @@ -2,4 +2,9 @@ AudioMixerRetry: cron: 0 * * * * class: "JamRuby::AudioMixerRetry" - description: "Retries mixes that set the should_retry flag or never started" \ No newline at end of file + 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"