Saturday, August 21, 2010

Ruby on Rails on CentOS 5.5 with REE/Phusion Passenger

Install (minimal) gcc toolchain


$ sudo yum install autoconf automake binutils gcc gcc-c++

Install Ruby Enterprise Edition (REE)


Download Ruby Enterprise Edition


Extract & run installer


tar xzvf ruby-enterprise-X.X.X.tar.gz
./ruby-enterprise-X.X.X/installer

Welcome to the Ruby Enterprise Edition installer
This installer will help you install Ruby Enterprise Edition 1.8.7-2010.02.
Don't worry, none of your system files will be touched if you don't want them
to, so there is no risk that things will screw up.

You can expect this from the installation process:

1. Ruby Enterprise Edition will be compiled and optimized for speed for this
system.
2. Ruby on Rails will be installed for Ruby Enterprise Edition.
3. You will learn how to tell Phusion Passenger to use Ruby Enterprise
Edition instead of regular Ruby.

Press Enter to continue, or Ctrl-C to abort.

Checking for required software...

* C compiler... found at /usr/bin/gcc
* C++ compiler... found at /usr/bin/g++
* The 'make' tool... found at /usr/bin/make
* The 'patch' tool... found at /usr/bin/patch
* Zlib development headers... not found
* OpenSSL development headers... not found
* GNU Readline development headers... not found

Some required software is not installed.
But don't worry, this installer will tell you how to install them.
Press Enter to continue, or Ctrl-C to abort.

--------------------------------------------
Installation instructions for required software

* To install Zlib development headers:
Please run yum install zlib-devel as root.

* To install OpenSSL development headers:
Please run yum install openssl-devel as root.

* To install GNU Readline development headers:
Please run yum install readline-devel as root.

REE installer gives me a nice list of dependencies I am missing. So lets install them


$ sudo yum install zlib-devel openssl-devel readline-devel 


Checking for required software...

* C compiler... found at /usr/bin/gcc
* C++ compiler... found at /usr/bin/g++
* The 'make' tool... found at /usr/bin/make
* The 'patch' tool... found at /usr/bin/patch
* Zlib development headers... found
* OpenSSL development headers... found
* GNU Readline development headers... found
--------------------------------------------
Target directory
Where would you like to install Ruby Enterprise Edition to?
(All Ruby Enterprise Edition files will be put inside that directory.)
[/opt/ruby-enterprise-1.8.7-2010.02] :

I am fine with this location, so I hit enter


Warning: some libraries could not be installed
The following gems could not be installed, probably because of an Internet
connection error:

* mysql
* sqlite3-ruby
* pg

These gems are not required, i.e. Ruby Enterprise Edition will work fine without them. But most people use Ruby Enterprise Edition in combination with Phusion Passenger and Ruby on Rails, which do require one or more of the aforementioned gems, so you may want to install them later.

To install the aforementioned gems, please use the following commands:
* /opt/ruby-enterprise-1.8.7-2010.02/bin/ruby /opt/ruby-enterprise-1.8.7-2010.02/bin/gem install mysql
* /opt/ruby-enterprise-1.8.7-2010.02/bin/ruby /opt/ruby-enterprise-1.8.7-2010.02/bin/gem install sqlite3-ruby
* /opt/ruby-enterprise-1.8.7-2010.02/bin/ruby /opt/ruby-enterprise-1.8.7-2010.02/bin/gem install pg

I don’t need Postgress, but I do need the other two


$ sudo yum install mysql-server mysql mysql-devel
$ sudo yum install sqlite-devel

Configure mysqld to run at system startup


$ sudo /sbin/chkconfig --list | grep mysqld
mysqld 0:off 1:off 2:off 3:off 4:off 5:off 6:off
$ sudo /sbin/chkconfig --level 35 mysqld on

Now install the necessary gems


$ sudo /opt/ruby-enterprise-1.8.7-2010.02/bin/ruby /opt/ruby-enterprise-1.8.7-2010.02/bin/gem install mysql
Building native extensions. This could take a while...
Successfully installed mysql-2.8.1
1 gem installed

And sqlite3-ruby


$ sudo /opt/ruby-enterprise-1.8.7-2010.02/bin/ruby /opt/ruby-enterprise-1.8.7-2010.02/bin/gem install sqlite3-ruby
Building native extensions. This could take a while...
ERROR: Error installing sqlite3-ruby:
ERROR: Failed to build gem native extension.

/opt/ruby-enterprise-1.8.7-2010.02/bin/ruby extconf.rb
checking for sqlite3.h... yes
checking for sqlite3_libversion_number() in -lsqlite3... yes
checking for rb_proc_arity()... no
checking for sqlite3_initialize()... no
sqlite3-ruby only supports sqlite3 versions 3.6.16+, please upgrade!

Looks like sqlite3-ruby needs a higher version of sqlite. So I built sqlite from source


$ wget http://www.sqlite.org/sqlite-amalgamation-3.7.0.1.tar.gz
$ tar xvzf sqlite-amalgamation-3.7.0.1.tar.gz
$ cd sqlite-3.7.0.1
$ ./configure --prefix=/opt/local/sqlite-3.7.0.1
$ make
$ sudo make install

And now re-install sqlite3-ruby gem


$ sudo /opt/ruby-enterprise-1.8.7-2010.02/bin/gem install sqlite3-ruby -- --with-sqlite3-dir=/opt/local/sqlite-3.7.0.1 
Building native extensions. This could take a while...
Successfully installed sqlite3-ruby-1.3.1
1 gem installed

Install Apache


$ sudo yum install httpd-devel

Configure httpd to start at system startup


$ sudo /sbin/chkconfig --list | grep httpd
httpd 0:off 1:off 2:off 3:off 4:off 5:off 6:off
$ sudo /sbin/chkconfig --level 35 httpd on

Start httpd


$ sudo /sbin/service httpd start
Starting httpd: [ OK ]

Open port 80 on the firewall


$ vi /etc/sysconfig/iptables
# Firewall configuration written by system-config-securitylevel
# Manual customization of this file is not recommended.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:RH-Firewall-1-INPUT - [0:0]
-A INPUT -j RH-Firewall-1-INPUT
-A FORWARD -j RH-Firewall-1-INPUT
-A RH-Firewall-1-INPUT -i lo -j ACCEPT
-A RH-Firewall-1-INPUT -p icmp --icmp-type any -j ACCEPT
-A RH-Firewall-1-INPUT -p 50 -j ACCEPT
-A RH-Firewall-1-INPUT -p 51 -j ACCEPT
-A RH-Firewall-1-INPUT -p udp --dport 5353 -d 224.0.0.251 -j ACCEPT
-A RH-Firewall-1-INPUT -p udp -m udp --dport 631 -j ACCEPT
-A RH-Firewall-1-INPUT -p tcp -m tcp --dport 631 -j ACCEPT
-A RH-Firewall-1-INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A RH-Firewall-1-INPUT -j REJECT --reject-with icmp-host-prohibited
COMMIT

Notice, I added the line -A RH-Firewall-1-INPUT -p tcp -m tcp --dport 80 -j ACCEPT

Restart firewall for changes to take effect


$ sudo /sbin/service iptables restart
Flushing firewall rules: [ OK ]
Setting chains to policy ACCEPT: filter [ OK ]
Unloading iptables modules: [ OK ]
Applying iptables firewall rules: [ OK ]
Loading additional iptables modules: ip_conntrack_netbios_n[ OK ]

Install Phusion Passenger (modrails)

See Phusion Passenger for further details


$ sudo gem install passenger
[sudo] password for amitava:
Building native extensions. This could take a while...
Successfully installed passenger-2.2.15
1 gem installed
Installing ri documentation for passenger-2.2.15...
Installing RDoc documentation for passenger-2.2.15...
[amitava@gestpedia-dev test]$ sudo passenger-install-apache2-module
The Apache 2 module was successfully installed.
Please edit your Apache configuration file, and add these lines:
LoadModule passenger_module /opt/ruby-enterprise-1.8.7-2010.02/lib/ruby/gems/1.8/gems/passenger-2.2.15/ext/apache2/mod_passenger.so
PassengerRoot /opt/ruby-enterprise-1.8.7-2010.02/lib/ruby/gems/1.8/gems/passenger-2.2.15
PassengerRuby /opt/ruby-enterprise-1.8.7-2010.02/bin/ruby

After you restart Apache, you are ready to deploy any number of Ruby on Rails
applications on Apache, without any further Ruby on Rails-specific
configuration!

Add mod_passenger configuration


$ sudo vi /etc/httpd/conf.d/modrails.conf

LoadModule passenger_module /opt/ruby-enterprise-1.8.7-2010.02/lib/ruby/gems/1.8/gems/passenger-2.2.15/ext/apache2/mod_passenger.so
PassengerRoot /opt/ruby-enterprise-1.8.7-2010.02/lib/ruby/gems/1.8/gems/passenger-2.2.15
PassengerRuby /opt/ruby-enterprise-1.8.7-2010.02/bin/ruby

$ sudo /sbin/service httpd restart
Stopping httpd: [ OK ]
Starting httpd: httpd: Syntax error on line 210 of /etc/httpd/conf/httpd.conf: Syntax error on line 1 of /etc/httpd/conf.d/modrails.conf: Cannot load /opt/ruby-enterprise-1.8.7-2010.02/lib/ruby/gems/1.8/gems/passenger-2.2.15/ext/apache2/mod_passenger.so into server: /opt/ruby-enterprise-1.8.7-2010.02/lib/ruby/gems/1.8/gems/passenger-2.2.15/ext/apache2/mod_passenger.so: failed to map segment from shared object: Permission denied
[FAILED]

Oops! I hit trusted google and get the following links

Goolge group thread

modrails User Guide

So SELinux is the culprit. Setup the SELinux context


$ sudo chcon -R -h -t httpd_sys_content_t /opt/ruby-enterprise-1.8.7-2010.02
$ sudo /sbin/service httpd restart
Stopping httpd: [FAILED]
Starting httpd: [ OK ]

Deploy the rails application

See User Guide

Create a directory for your webapps, and move (or copy) your app to that location


$ sudo mkdir /var/www/webapps
$ sudo mv coolapp /var/www/webapps/
$ sudo ln -s /var/www/webapps/coolapp/public /var/www/html/coolapp
$ sudo vi /etc/httpd/conf.d/coolapp.conf
<VirtualHost *:80>
ServerName www.coolapp.com
RailsBaseURI /coolapp
<Directory /var/www/html/coolapp>
Options -MultiViews
</Directory>
</VirtualHost>

$ sudo /sbin/service httpd restart

$ sudo tail -f /var/log/httpd/error_log
[Fri Aug 20 15:35:45 2010] [notice] caught SIGTERM, shutting down
[Fri Aug 20 15:35:45 2010] [notice] SELinux policy enabled; httpd running as context user_u:system_r:httpd_t:s0
[Fri Aug 20 15:35:45 2010] [notice] suEXEC mechanism enabled (wrapper: /usr/sbin/suexec)
[Fri Aug 20 15:35:45 2010] [error] *** Passenger could not be initialized because of this error: Cannot create FIFO file /tmp/passenger.20709/.guard: Permission denied (13)
[Fri Aug 20 15:35:45 2010] [notice] Digest: generating secret for digest authentication ...
[Fri Aug 20 15:35:45 2010] [notice] Digest: done
[Fri Aug 20 15:35:45 2010] [error] *** Passenger could not be initialized because of this error: Cannot create FIFO file /tmp/passenger.20711/.guard: Permission denied (13)

StackOverflow

This is still an SELinux issue. I found a working selinux policy here
I am copying it here for easy reference


$ vi passenger.te
module passenger 1.0;
require {
type httpd_tmp_t;
type devpts_t;
type httpd_sys_script_t;
type security_t;
type httpd_t;
type unconfined_t;
type selinux_config_t;
type hi_reserved_port_t;
type httpd_sys_content_t;
type var_t;
type cert_t;
class file { getattr read create append };
class process { siginh signal noatsecure rlimitinh };
class unix_stream_socket { read write shutdown };
class chr_file { read write };
class capability { setuid dac_override chown fsetid setgid fowner };
class fifo_file { setattr create getattr unlink };
class sock_file { write getattr setattr create unlink };
class lnk_file { read getattr };
class udp_socket name_bind;
class dir { write read search add_name };
}

#============= httpd_sys_script_t ==============
allow httpd_sys_script_t cert_t:dir search;
allow httpd_sys_script_t cert_t:file { read getattr };
allow httpd_sys_script_t cert_t:lnk_file read;
allow httpd_sys_script_t devpts_t:chr_file { read write };
allow httpd_sys_script_t httpd_sys_content_t:fifo_file setattr;
allow httpd_sys_script_t httpd_sys_content_t:sock_file { create unlink setattr };
allow httpd_sys_script_t httpd_t:unix_stream_socket { read write };
allow httpd_sys_script_t httpd_tmp_t:fifo_file setattr;
allow httpd_sys_script_t httpd_tmp_t:sock_file { write create unlink setattr };
allow httpd_sys_script_t self:capability { setuid chown fsetid setgid fowner dac_override };
allow httpd_sys_script_t unconfined_t:process signal;
allow httpd_sys_script_t var_t:dir { write read add_name };
allow httpd_sys_script_t var_t:file { read getattr create append };
#============= httpd_t ==============
allow httpd_t hi_reserved_port_t:udp_socket name_bind;
allow httpd_t httpd_sys_content_t:fifo_file { create unlink getattr setattr };
allow httpd_t httpd_sys_content_t:sock_file { getattr unlink setattr };
allow httpd_t httpd_sys_script_t:process { siginh rlimitinh noatsecure };
allow httpd_t httpd_sys_script_t:unix_stream_socket { read write shutdown };
allow httpd_t httpd_tmp_t:fifo_file { create unlink getattr setattr };
allow httpd_t httpd_tmp_t:sock_file { getattr unlink setattr };
allow httpd_t security_t:dir search;
allow httpd_t self:capability { fowner fsetid };
allow httpd_t selinux_config_t:dir search;
allow httpd_t var_t:file { read getattr };
allow httpd_t var_t:lnk_file { read getattr };

$ checkmodule -M -m -o passenger.mod passenger.te
$ semodule_package -o passenger.pp -m passenger.mod
$ sudo /usr/sbin/semodule -i passenger.pp

$ sudo /sbin/service httpd restart

Thursday, August 19, 2010

Scala wrappers for java collection


import scala.collection.JavaConversions._

System.getProperties.foreach(println)


Please note that the java method System.getProperties() returns java.util.Properties. There is no foreach defined in that class. Behind the scene, scala.collection.JavaConversions defines implicit conversions that converts java collections to scala collections.

Friday, August 6, 2010

Python titbits

I was generating sql data load scripts from excel documents using python. Here’s the code snippet
import csv

with open('cases.csv', 'rU') as f:
# r = csv.DictReader(f, dialect=csv.excel)
r = csv.reader(f, dialect=csv.excel)
cases = [row[:-3] for row in r]

Few interesting aspects in the code above

  1. I am using python’s with statement to automatically close the csv file after use.

  2. I am opening the file in ‘rU’ mode - i.e. readonly and Universal mode. ‘U’ is critical for csv module to parse excel generated csv’s. Here’s the gory details

  3. Use csv.DictReader if you want data in a dictionary keyed by the column names. DictReader exposes a
    convenient property ‘fieldnames’ that has the column names if your csv has a header row.


  4. Notice the use of python’s list comprehension to populate the cases array. Also, I am chopping
    off the last 3 columns (which was empty in the data file) using python’s slice operator.



For better clarity, I changed the code to
import csv
import collections

Case = collections.namedtuple('Case',"program, title, meeting_time, meeting_date")
with open('foo.csv', 'rU') as f:
# r = csv.DictReader(f, dialect=csv.excel)
r = csv.reader(f, dialect=csv.excel)
cases = [Case(*row[:-3]) for row in r]

namedtuple is similar to ruby’s Struct class.
This is nicely encapsulated now. The rest of the code need not worry about which column
the data was in the original data file. Also note the use * in Case(*row[:-3]). I am using * to pass the parsed array as arguments to Case. In clojure, you would use apply

Now consumers of cases can use it like so
qry = '''INSERT INTO cases (program, meeting_time, meeting_date) VALUES ('%s','%s','%s');'''
for c in cases:
print qry%(c.program, c.meeting_time, c.meeting_date)

Before I wrap up, one last thing - if you want to output this to a file instead of stdout
with open('cases.sql', 'w') as f:
qry = '''INSERT INTO cases (program, meeting_time, meeting_date) VALUES ('%s','%s','%s');'''
for c in cases:
print >>f, qry%(c.program, c.meeting_time, c.meeting_date)

Notice how I am redirecting print output to the file handle ‘f’. Here’s a blog article about it.