Got NFS working

Feb 06 2010 Published by Eneko Alonso under uncategorized

Well, last night I started reading about how to share the files folder of a Drupal installation on a site with multiple web servers. Seems like NFS is the right way to go (as long as your traffic doesn’t grow too much). Perfect for Spaniards.es, since I want to move from a 2 dedicated server setup to a more flexible one where I can launch new web servers easily, almost on demand.

  1. # Install NFS
  2. yum -y install rpcbind nfs-utils nfs-utils-lib system-config-nfs rsync
  3.  
  4. # chkconfig nfs on
  5. /sbin/chkconfig nfs on
  6.  
  7. vi /etc/idmapd.conf
  8. # [General]
  9. # Domain = spaniards.es
  10. # [Mapping]
  11. # Nobody-User = nfsnobody
  12. # Nobody-Group = nfsnobody
  13.  
  14. service rpcidmapd restart
  15.  
  16. echo "/files/archivos  /nfs4exports/archivos none bind 0 0" >> /etc/fstab
  17. mount /nfs4exports/archivos
  18.  
  19. echo "/nfs4exports XXX.177.133.25(rw,insecure,no_subtree_check,nohide,fsid=0) XXX.177.136.14(rw,insecure,no_subtree_check,nohide,fsid=0)" > /etc/exports
  20. echo "/nfs4exports/archivos XXX.177.133.25(rw,insecure,no_subtree_check,nohide) XXX.177.136.14(rw,insecure,no_subtree_check,nohide)" >> /etc/exports
  21. su -c "/usr/sbin/exportfs -rva"
  22. /sbin/service nfs restart
  23.  
  24. echo 'portmap:ALL' > /etc/hosts.deny
  25. echo 'lockd:ALL' >> /etc/hosts.deny
  26. echo 'mountd:ALL' >> /etc/hosts.deny
  27. echo 'rquotad:ALL' >> /etc/hosts.deny
  28. echo 'statd:ALL' >> /etc/hosts.deny
  29.  
  30. echo 'portmap:XXX.177.133.25,XXX.177.136.14' > /etc/hosts.allow
  31. echo 'lockd:XXX.177.133.25,XXX.177.136.14' >> /etc/hosts.allow
  32. echo 'mountd:XXX.177.133.25,XXX.177.136.14' >> /etc/hosts.allow
  33. echo 'rquotad:XXX.177.133.25,XXX.177.136.14' >> /etc/hosts.allow
  34. echo 'statd:XXX.177.133.25,XXX.177.136.14' >> /etc/hosts.allow
  35.  
  36. echo 'LOCKD_TCPPORT=48620' >> /etc/sysconfig/nfs
  37. echo 'LOCKD_UDPPORT=48620' >> /etc/sysconfig/nfs
  38. echo 'MOUNTD_PORT=48621' >> /etc/sysconfig/nfs
  39. echo 'STATD_PORT=48622' >> /etc/sysconfig/nfs
  40. echo 'RQUOTAD=no' >> /etc/sysconfig/nfs
  41. echo 'RQUOTAD_PORT=48623' >> /etc/sysconfig/nfs

At the end, setting up NFS is not that complicated, but it was my first time, so it took a while. Actually, I had everything working before I noticed, since, logged in as root, I couldn’t figure out why I wasn’t able to write to the nfs mounted directory, being this mounted as rw. Well, turns out that you shouldn’t do this as root, since root access on nfs, once enabled does not require authentication.

I’m not sure why there is a need to mount a bind directory on the nfs server, but I think it has to do with the NFS4 directory configuration.

I think I got all the information I needed between these two links:
http://fconfig.wordpress.com/2006/08/17/setting-up-a-fedora-nfs-server/
http://fedorasolved.org/Members/renich/howtos/f7/en/nfsv4-fedora/?searchterm=nfs

Next step: set up the round robin load balancer: haproxy or dns?

No responses yet

Un Navarro en California is back!

Jan 29 2010 Published by Eneko Alonso under uncategorized

Yeah baby, I’m back. I have restored the Drupal installation I had for my personal blog, kinda missed it a lot. Hopefully all the old links will still work.
Visit: Un Navarro en California

No responses yet

Drupal: users can’t access content! (node_access gone wild)

Aug 08 2009 Published by Eneko Alonso under uncategorized

If you are having trouble with users accessing content, check out the node_access table and make sure all permissions are set properly. Unless you are using modules like Organic Groups which set the node access permissions by user, you will only need one row on that table, with user id 0:

Restore permissions:

  1. TRUNCATE TABLE node_access;
  2. INSERT INTO node_access VALUES (0, 0, 'all', 1, 0, 0);

No responses yet

Upgrading Spaniards.es to Drupal 6

Feb 24 2009 Published by Eneko Alonso under uncategorized

Last Saturday I decided to upgrade Spaniards.es to Drupal 6. I’ve been waiting for a long time, since Drupal 6 came out, basically because this site uses a lot of modules which weren’t available yet for the new version. Recently drupal.org was also upgraded and that was a decisive point for me.

It took me five hours to upgrade, but most of the process was making sure I had good backups copied on both servers but also downloaded to my personal computer. It takes a while to download a site like this!

Here is my log:

  1. Set Site Maintenance
  2. Set .htaccess maintenance redirect
  3. Stop MYSQL
  4. Backup spdba_main
  5. Backup public_html
  6. Rename public_html to public_html_5.15
  7. Create sym link to public_html: ln -s public_html_5.15 public_html
  8. Grab list of active modules select filename, status from system where filename like '%.module' and status = 1;
  9. Disable all non-core modules
  10. Download Drupal 6.9
  11. Delete sym link rm public_html
  12. Create sym link to Drupal 6 ln -s drupal-6.9 public_html
  13. Copy default.settings.php to settings.php
  14. Update DB connection string
  15. Process to upgrade Drupal: www.spaniards.es/update.php

Once the DB was updated, it was a matter of installing all the modules and upgrading the DB when needed. Most of the staff worked fine. I had to do some fixes on my custom modules, specially for the new menu hooks.

Drupal 6 rocks.

5 responses so far

vancode2int

Feb 24 2009 Published by Eneko Alonso under uncategorized

vancode2int is a very simple but nice function I was looking for for a while and I had no idea Drupal included. It basically decodes the thread id of a comment in Drupal converting it back to an integer, which is very useful when displaying comments on a plain list, instead of on a threaded conversation tree.

No responses yet

Keyboard shortcuts in Drupal: Navigating through comments

Dec 14 2008 Published by Eneko Alonso under uncategorized

It was one of the best features of Google Reader and Google GMail: the keyboard shortcuts, specially the ones to navigate through messages or posts (n = next, p = previous).

I decided it was time to implement it on spaniards.es, so here is the code I used. So far it only navigates through comments back and forth on the same page (does not go to next page or so). I am planning to add other shortcuts soon.

  1. $(document).keyup(function(event) {
  2.  
  3.   if (event.target.tagName == "INPUT" ||
  4.       event.target.tagName == "TEXTAREA")
  5.     return;
  6.  
  7.   var selected = $('.comment-selected');
  8.  
  9.   // Next comment
  10.   if (event.keyCode == 78) {
  11.     if (selected.length) {
  12.       selected.removeClass('comment-selected');
  13.       selected = selected.nextAll('.comment:first');
  14.       selected.addClass('comment-selected');
  15.     } else {
  16.       selected = $('.comment:first').addClass('comment-selected');
  17.     }
  18.     if (selected.length) {
  19.       window.scrollTo(0, selected.offset().top);
  20.     }
  21.     return;
  22.   }
  23.  
  24.   // Previous comment
  25.   if (event.keyCode == 80) {
  26.     if (selected.length) {
  27.       selected.removeClass('comment-selected');
  28.       selected = selected.prevAll('.comment:first');
  29.       selected.addClass('comment-selected');
  30.     } else {
  31.       selected = $('.comment:last').addClass('comment-selected');
  32.     }
  33.     if (selected.length) {
  34.       window.scrollTo(0, selected.offset().top);
  35.     }
  36.     return;
  37.   }
  38. });

As you can see, it is important to leave the input and textarea fields alone, so the keyboard shortcuts don’t interfere with the user typing some text. With a little bit of CSS the selected comment can be displayed with a different border color or background. And that’s it!

Check an example topic at www.spaniards.es

2 responses so far

Ajax comments in Drupal 5: How I made it – Part III

Dec 13 2008 Published by Eneko Alonso under uncategorized

In Ajax comments in Drupal 5: How I made it parts I and II we saw the code needed on the back-end. Now, let’s see what goes on the client side: the Javascript.

Submitting the comment with an ajax request

Basically, to add a comment we need the node id (nid) and the comment text. The code in the back-end will handle the user info and permissions. The first thing we do is find the comment form and the submit button. Then we hook the click event on the submit button.

  1.   var commentForm = jQuery('form#comment-form');
  2.   if (!commentForm.length) return;
  3.   var submitBtn = jQuery('#edit-submit');
  4.   if (!submitBtn.length) return;
  5.  
  6.   submitBtn.click(function(ev)
  7.   {
  8.     // …
  9.   }

When the user clicks the submit button, we get the node id (we could have done on domready) and the comment text. If the comment is empty, we don’t do anything, so the comment will be submitted the standard way and Drupal will fail the comment form validation.

  1.     // Get Node Id
  2.     var nid = jQuery('#node-nid').text();
  3.     if (!nid) return true; // Do nothing…
  4.     if (parseInt(nid) <= 0) return true;
  5.  
  6.     // Get Comment Text
  7.     var text = jQuery('#edit-comment').val();
  8.     if (!text) return true; // Let Drupal validate the form

Since we don’t want to get back just the comment added but all the new comments added by any user on this node, we want to pass the last comment id on screen.

  1.     // Get last comment Id
  2.     var lastComment = jQuery('div.comment:last');
  3.     var lastCommentId = (lastComment.length)? lastComment.id().split('-')[1] : 0;

Due to my limited knowledge of jQuery at the time, I used the load function for the ajax request. The problem with this function is that, from what I know, it only works on elements already included in the DOM. So before the request, I had to create a container for the new comments. The point of insertion will differ if the page already has comments or not:

  1.     // We got both nid and text. Let's create the comment holder
  2.     var comment = jQuery(document.createElement('div'));
  3.     comment.hide();
  4.     if (lastComment.length) {
  5.       lastComment.after(comment);
  6.     } else {
  7.       comment.insertBefore(jQuery('div.box'));
  8.     }

Finally, the load request:

  1.     // Ajax submit/load
  2.     comment.load('/add/comment',
  3.       {  'nid': nid,
  4.         'text': text,
  5.         'lastcid': lastCommentId
  6.       },
  7.       function(responseText, textStatus, XMLHttpRequest) {
  8.         if (textStatus == 'success' && responseText) {
  9.           ajax_insert_comment(comment);
  10.         } else {
  11.           comment.remove();
  12.           alert("Error: No se pudo enviar el comentario");
  13.         }
  14.       }
  15.     );

If the request failed, the container was removed. If the request succeeded, the new comments were added to the page:

  1. ajax_insert_comment = function(comment) {
  2.   // Insert new comments
  3.   comment.show();
  4. };

And that was it! Ajax comments in Drupal working like a charm.

Adding/removing the “new” indicator

Now that I had my Ajax comments working I wanted them to look better. For example, I wanted the new added comments to have the “new” indicator, while at the same time I wanted to remove the indicator from the comments already on the screen. On ajax_insert_comment:

  1.   // Clear 'new' from previous comments
  2.   jQuery('div.comment').find('span.new').remove();
  3.   // Add 'new' indicator
  4.   var newcomment = jQuery(document.createElement('span'));
  5.   newcomment.addClass('new').html('new');
  6.   newcomment.insertBefore(comment.find('div.comment-author'));

And many other tweaks like that.

The code

Here is the whole code for ajax comment submission I currently use:

  1. ajax_comments = function() {
  2.   var commentForm = jQuery('form#comment-form');
  3.   if (!commentForm.length) return;
  4.   var submitBtn = jQuery('#edit-submit');
  5.   if (!submitBtn.length) return;
  6.  
  7.   submitBtn.val('Enviar comentario');
  8.   submitBtn.click(function(ev)
  9.   {
  10.     // Disable submit button
  11.     submitBtn.val('Enviando…').attr("disabled","disabled");
  12.  
  13.     // Clear the 'edit' link from previous comments
  14.     jQuery('li.edit-comment-link').remove();
  15.  
  16.     // Get Node Id
  17.     var nid = jQuery('#node-nid').text();
  18.     if (!nid) return true; // Do nothing…
  19.     if (parseInt(nid) <= 0) return true;
  20.  
  21.     // Get Comment Text
  22.     var text = jQuery('#edit-comment').val();
  23.     if (!text) return true; // Let Drupal validate the form
  24.  
  25.     // Get last comment Id
  26.     var lastComment = jQuery('div.comment:last');
  27.     var lastCommentId = (lastComment.length)? lastComment.id().split('-')[1] : 0;
  28.  
  29.     // // We got both nid and text. Let's create the comment holder
  30.     var comment = jQuery(document.createElement('div'));
  31.     comment.hide();
  32.     if (lastComment.length) {
  33.       lastComment.after(comment);
  34.     } else {
  35.       comment.insertBefore(jQuery('div.box'));
  36.     }
  37.  
  38.     // Ajax submit/load
  39.     comment.load('/add/comment',
  40.       {  'nid': nid,
  41.         'text': text,
  42.         'lastcid': lastCommentId
  43.       },
  44.       function(responseText, textStatus, XMLHttpRequest) {
  45.         if (textStatus == 'success' && responseText) {
  46.           ajax_insert_comment(comment);
  47.           jQuery('#edit-comment').val('');
  48.         } else {
  49.           comment.remove();
  50.           alert("Error: No se pudo enviar el comentario");
  51.         }
  52.         // Reenable submit button
  53.         submitBtn.val('Enviar comentario').removeAttr("disabled");
  54.       }
  55.     );
  56.  
  57.     return false;
  58.   });
  59. };
  60.  
  61. ajax_insert_comment = function(comment) {
  62.   // Clear 'new' from previous comments
  63.   jQuery('div.comment').find('span.new').remove();
  64.   // Add 'new' indicator
  65.   var newcomment = jQuery(document.createElement('span'));
  66.   newcomment.addClass('new').html('new');
  67.   newcomment.insertBefore(comment.find('div.comment-author'));
  68.   // Remove comment count
  69.   comment.find('span.commentcount').empty();
  70.   // Insert new comments
  71.   comment.show();
  72.   jQuery('#edit-comment').focus();
  73. };
  74.  
  75. jQuery(document).ready(function() {
  76.   ajax_comments();
  77. });

As you can see, when I wrote this code back in July, I have just started using jQuery and I didn’t know much about event handling. So I used the old return true/false to stop the submit event if needed. Also, now that I see the code again I can think on better ways to do parts of it. Well, that’s a good sign: it means I’m getting better :)

Series:
Ajax comments in Drupal 5: How I made it – Part I
Ajax comments in Drupal 5: How I made it – Part II
Ajax comments in Drupal 5: How I made it – Part III

See the ajax comments in action

Please, note this is a WordPress based blog. The Drupal website where I have implemented the ajax comments is www.spaniards.es

10 responses so far

Ajax comments in Drupal 5: How I made it – Part II

Dec 11 2008 Published by Eneko Alonso under uncategorized

In Axaj comments in Drupal 5: How I made it – Part I I covered the most important part of the module code: saving comments to the database. Now, let’s see how to display them properly.

Rendering the new comment

Comments usually have more information than the user name and the comment text. In my website, for instance, comments have information like the user registered date, where he/she lives, the country flag, the comment number on the thread, etc. All this information could not be made up at the client side; the server had to send it back to the client once the comment was saved.

Also, we want to take full advantage of Drupal: we want the corresponding filters applied to the comment text to remove malicious HTML tags, convert plain URLs into HTML links, etc.

The best way to do this is to use the function theme with the parameter ‘comment_view’. The difference between theme(‘comment’,…) and theme(‘comment_view’,…) is that filters are applied only in the second case, if I am not mistaken.

  1. function _spaniards_get_latest_comments($nid, $fromcid) {
  2.   $result = db_query('select * from comments where nid = %d and cid > %d', $nid, $fromcid);
  3.   $content = '';
  4.   while ($comment = db_fetch_object($result)) {
  5.     if ($comment->status == COMMENT_NOT_PUBLISHED) continue;
  6.     $links = module_invoke_all('link', 'comment', $comment, 1);
  7.     $content .= theme('comment_view', $comment, $links);
  8.   }
  9.   return $content;
  10. }

As I mentioned before, we don’t want to return just the comment added by the user but any comment added by any other user since the user loaded the page. This function will return any comment on the thread with comment Id greater than the parameter $fromcid.

Besides using theme(‘comment_view’,…) it is a good idea also to invoke the link hook. This will let other modules like fasttoggle inject their very useful links in out comment.

And that is it for now. This code, together with the code on Part I will be the only PHP needed. We will see the client side Javascript code on Part III.

Series:
Ajax comments in Drupal 5: How I made it – Part I
Ajax comments in Drupal 5: How I made it – Part II
Ajax comments in Drupal 5: How I made it – Part III

See the ajax comments in action

Please, note this is a WordPress based blog. The Drupal website where I have implemented the ajax comments is www.spaniards.es

No responses yet

Ajax comments in Drupal 5: How I made it – Part I

Dec 10 2008 Published by Eneko Alonso under uncategorized

First of all, I would like to apologize for not publishing this the right way. I haven’t had time to put it all on a standalone module, which will make it easier to share the code at www.drupal.org. Feel free to grab the code and put it on a module if you wish (and let me know if you do so :)

Why Ajax comments?

Ajax comments let users to submit comments without having to reload the whole page. Reloading a page is not only slow, but its an overload for the server which has to process and send the whole page again just to add a simple comment to the DOM. So yes, Ajax comments are a big benefit, both for the user, which has to wait less, and for the server, which has to process and send less information.

Available modules

Point explained, lets see how it went. In the past I tried the Javascript Tools set of modules, which had other dependencies if I am not mistaken. And I don’t know why but it didn’t work at all for me. My comments kept reloading the whole page while sent. A couple of months ago I decided it was time to do it myself.

The plan

My original plan was simple: when the user hits the submit button, send the comment to the server, save it and add it to the DOM at the end of the comment list. This looked simple, but it had a few thing to deal with. I didn’t want to mess with Drupal’s comment module neither modify any line of Drupal’s core. So I decided to take an alternative path.

Saving a comment

Saving a comment doesn’t require much information. Since the user has to be logged in, the only information needed is basically the node Id and the comment itself. Drupal has a very nice hook_menu that allows you to create pages with a specific URL. These pages can process data, serve data or both. The only drawback, if so, was that hooks only works on modules, so I had to create my own. I decided to add it to my custom site module.

  1. function spaniards_menu($may_cache) {
  2.   $items = array();
  3.   if (!$may_cache) {
  4.     // Add comment
  5.     $items[] = array(
  6.       'path' => 'add/comment',
  7.       'title' => 'Add Comment',
  8.       'callback' => 'spaniards_add_comment',
  9.       'callback arguments' => array(),
  10.       'access' => user_access("post comments"),
  11.       'type' => MENU_CALLBACK,
  12.     );
  13.   }
  14. }
  15.  
  16. function spaniards_add_comment() {
  17.    global $user;
  18.    $text = $_REQUEST['text'];
  19.    $comment = array();
  20.    $comment['uid'] = $user->uid;
  21.    $comment['nid'] = $_REQUEST['nid'];
  22.    $comment['pid'] = 0;
  23.    $comment['subject'] = substr($text, 0, 20);
  24.    $comment['comment'] = $text;
  25.    // Add comment to DB
  26.    $cid = comment_save($comment);
  27.  
  28.    // Mark the node as viewed by the user
  29.    node_tag_new($comment['nid']);
  30.  
  31.    // Get all comments since the last one
  32.    $fromcid = isset($_REQUEST['lastcid'])? $_REQUEST['lastcid'] : 0;
  33.    print _spaniards_get_latest_comments($comment['nid'], $fromcid);
  34.  
  35.    exit;
  36. }

Just with those two functions we can bypass Drupal’s comment submission system and add new comments to the database. As long as we use the comment_save function, the actual process involved in saving a comment is safe (hooks get called, watchdog logging works, etc). Also, hook_menu guarantees only requests with the right access will be able to add a comment (in this case the user_access function verifies the user has “post comments” permissions granted).

Once the comment is saved, the node is marked as viewed by the user, to remove all “new” labels from the new comments added.

Finally the comment is rendered and returned to the user. But wait! What if another user has posted a comment in between? During a conversation between multiple users this will happen very often. The solution: return all comments added since the last comment visible when the user visited the thread.

We will see how to do that next.

Series:
Ajax comments in Drupal 5: How I made it – Part I
Ajax comments in Drupal 5: How I made it – Part II
Ajax comments in Drupal 5: How I made it – Part III

See the ajax comments in action

Please, note this is a WordPress based blog. The Drupal website where I have implemented the ajax comments is www.spaniards.es

7 responses so far