2020-01-15

How To Create Temperature Monitor with Email Alerts

I need to monitor the temperature in a room and make sure it doesn't get too cold in the winter, so I decided to setup a raspberry pi as my temperature sensor, and create a simple application/service that regularly checks the temperature and updates me via email.

Once you know what you're doing it's really quite easy.  I had to do a lot of research, so it took me longer than I'd like to admit, but it was fun setting everything up and learning.  I stuck with python throughout because it simplifies everything.

Reading the Temperature 

First, you need a temperature sensor.  There's a really good tutorial by circuitbasics.com.  All you need is a raspberry pi 3b (older and cheaper ones probably work too), DS18B20, and a 4.7K resistor (anything between 1K and 10K would probably work).  I also used a 2x4 .1" female header for soldering the temperature sensor circuit so that I can easily connect it to the raspberry pi.

After connecting the temperature sensor circuit to the raspberry pi as shown in the tutorial and enabling the 1-wire interface, you'll be able to read the temperature sensor.

  1. Add this to the end of /boot/config.txt: dtoverlay=w1-gpio
  2. Reboot the raspberry pi
  3. Execute: sudo modprobe w1-gpio;sudo modprobe w1-therm
  4. Execute: cat /sys/bus/w1/devices/28-*/w1_slave
  5. The temperature is at the end (t=x) in thousandths of degrees Celsius
The tutorial also includes a bit of python code to read and output the temperature.

Web Service

The web service was extremely simple to create.  I used flask, and followed the hello world tutorial from pythonspot.com.  Then I added the ip address for external access, customized the port number and merged the temperature reading python code from before.  The web service I created simply returns the temperature in degrees Celsius because I wasn't interested in any other information.

Raspberry Pi Startup

Next, I wanted the python based web service to run on startup so that it'll always be available.  Following the example by instructables, I configured cron to start the service on boot.  To verify, I restarted the raspberry pi, went to the URL in my browser and made sure the temperature showed up.

Independent Polling Temperature Service

My last step was to setup a separate service on another computer that polls the temperature, verifies it, and notifies on error.  I'm using a different computer because I want to get a notification if the room gets cold (below zero), and the chips on the raspberry pi are only commercial grade (0 to 70C).  It would probably still work until -10C, but I still decided to keep it separate.
So I wrote a python service that reads the temperature, compares it to thresholds and sends an email if it's too cold.  I check the temperature hourly, but that's easily changed, I also have a very high temperature threshold because the temperature sensor reads hot (it's kept warmer than ambient by the raspberry pi) and I track the temperatures and email trends daily.  If reading the temperature ever fails I also send an email, which I found to have a lot of false positives so I added a couple of retries when reading the temperature.  I also write messages to syslog (I'm polling from a Linux box) so that I can easily see recent temperature readings.
I did fool around with setting up my polling application as a service that would automatically restart, but didn't fully get that working.

Anyway, here's my temperature poller.  It has been running for a couple of weeks without a glitch.

Source Email Address

One last note, I created a separate email address to send the notification emails from because I had to reduce the security on it.  I don't use the email address for anything except sending these emails so if it does get hacked it's not a big deal.

Let me know if you're interested in other source and I'll post that too.

2019-01-13

Accidentally Sending Emails to the Wrong Person?

Everyone has sent a message to the wrong person at least once.  Maybe it's an text message and you accidentally respond to the wrong conversation, or added the wrong name to an email.  Quite often it isn't a huge deal because of the fact we've all done it.  You'll receive a message that makes absolutely no sense, maybe responding with a "?", or you get a follow up soon after telling you to please ignore it.

Sending increasing personal information is embarrassing, and in business sending private or confidential information can be a huge problem.

In Outlook, it's quite easy to add a macro that double checks the recipients before an email is sent, comparing the domain name (ex. gmail.com, companyy.com, etc.).  Typically when I send emails to multiple recipients they have the same domain name, ignoring the domain of the company I work for.  

So here's what I use to make sure I don't accidentally CC the wrong person.  I added the VBA code at the bottom of the post to Outlook, and I get a verification popup every time I send an email to multiple domains that aren't in my ignoreDomains and ignoreStartsWith lists.

Another thing that can be done, which I do as well because quite often I think of extra things to add to an email right after sending it, is to add a rule to delay sends by X minutes.

Useful Links:



Option Explicit

Private Function ignoreDomains() As Variant
    ignoreDomains = Array("example.com", "example2.com")
End Function

Private Function ignoreStartsWith() As Variant
    ignoreStartsWith = Array("jay@trustworthy.com", "bob@trust.com")
End Function

Private Sub Application_ItemSend(ByVal objItem As Object, cancel As Boolean)
    Dim email As mailItem
    Dim warn As Boolean
    Dim message As String

    If ("MailItem" = TypeName(objItem)) Then
        Set email = objItem
        warn = domainMismatch(email, message)
        cancel = verifyCancelSend(warn, "This email is addressed to multiple domains." & vbCrLf & vbCrLf & message)
    End If
End Sub

Private Function verifyCancelSend(ByRef warn As Boolean, msg As String) As Boolean
    Dim retVal As Boolean
   
    If (warn) Then
        Beep
        retVal = (MsgBox(msg & vbCrLf & vbCrLf & "Would you like to cancel sending the email?", vbYesNo, "Cancel Send Confirmation") <> vbNo)
    End If
   
    verifyCancelSend = retVal
End Function

Private Function domainMismatch(mi As mailItem, message As String) As Boolean
    Dim retVal As Boolean
    Dim rc As Recipient
    Dim domain As String
    Dim baseDomain As String
    Dim ignoreStartValue As Variant
    Dim ignoreDomainValue As Variant
    Dim ignoreStart As Boolean
    Dim ignoreDomain As Boolean
   
    message = ""
    baseDomain = ""
    retVal = False

    For Each rc In mi.Recipients
        ignoreStart = False
        For Each ignoreStartValue In ignoreStartsWith()
            If (LCase(ignoreStartValue) = LCase(Left(rc.Address, Len(ignoreStartValue)))) Then
                ignoreStart = True
            End If
        Next
       
        If (Not ignoreStart) Then
            ignoreDomain = False
           
            domain = LCase(Right(rc.Address, Len(rc.Address) - InStr(rc.Address, "@")))
           
            For Each ignoreDomainValue In ignoreDomains()
                If (ignoreDomainValue = domain) Then
                    ignoreDomain = True
                End If
            Next
           
            If (Not ignoreDomain) Then
                If "" = baseDomain Then
                    message = rc.Address
                    baseDomain = domain
                End If
               
                If (baseDomain <> domain) Then
                    retVal = True
                    message = message & vbCrLf & rc.Address
                End If
            End If
        End If
    Next

    domainMismatch = retVal
End Function

2018-04-09

How to Setup Outline VPN at Home and Save the Cloud Hosting Costs

A while ago I had OpenVPN setup, but it was quite a bit of work.  Then I upgraded something and it broke, and I never got around to fixing it.  So setting up VPN access to my home network has been on my to do list for a while.  Then I noticed the release of Jigsaw's (Alphabet/Google) Outline VPN, and figured I'd give it a try.

Setting up Outline VPN took a lot longer than it should have because I didn't find a really good resource for installing it at home.  Everything I found left out some of the key details I was interested in.  Specifically, I wanted to know a bit about what's going on, and wanted to minimize the number of ports I have open and/or forwarded on my home router.  

First, I have Ubuntu 16.04, so the steps will be best described for that OS.  However, it shouldn't be that difficult to translate them to a different OS.
  1. Verify you have an externally accessible IP address.  
    1. Log into the router or cable modem your ISP provided
    2. Check the setup or status page
    3. There will be a WAN IP address.  If it's not within one of the following sets, private IP addresses, then you're good to go (10.0.0.0 - 10.255.255.255, 172.16.0.0 - 172.31.255.255, 192.168.0.0 - 192.168.255.255)
  2. Install Docker - Follow the link for the complete steps, or try the following:
    1. curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
    2. sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
    3. sudo apt-get update
    4. sudo apt-get install -y docker-ce
  3. The following steps generally following Outline VPN standard installation steps
  4. Download the install script and save it locally
    1. sudo wget https://raw.githubusercontent.com/Jigsaw-Code/outline-server/master/src/server_manager/install_scripts/install_server.sh > install_server.sh
  5. Set the port used by Outline Manager, by editing install_server.sh.  Then either update get_random_port to return a number between 1024 and 65535, or update the script at around line 99 to use the specific port number instead of calling get_random_port (which returns a random port)
    1. ex. Replace 'echo $num;' with 'echo 65333;'
  6. Update your router and/or firewall to forward the specific port to the computer that you're installing Outline on.
  7. Install Outline VPN, execute (-x is optional, and is for debugging purposes).  Take note of the apiUrl and certSha256 output at the end of the command assuming it's successful.
    1. cat install_server.sh | sudo bash -x
  8. Assuming the manager port is not blocked, the install will complete.
  9. Download Outline Manager from the following site
    1. https://getoutline.org/en/home
  10. Run Outline Manager and select the bottom option
  11. Enter the apiUrl and certSha256 from step 7 in the Outline Manager and click OK
  12. So that you can track usage, in the connected Outline Manager, add a key and give it a useful name.
  13. Click SHARE and copy the generated URL using the device you want to connect to your VPN
  14. The URL will look something like the following.  Again go to your firewall and/or router and forward the <PORT NUMBER> to the computer that you're installing Outline on.
    1. https://s3.amazonaws.com/outline-vpn/index.html#/invite/ss%3A%2F%2abcdefg...%3D%40<IP ADDRESS>%3A<PORT NUMBER>%2F%3Foutline%3D1
  15. Go to the URL from step 13.  You will be directed to install the Outline application, and add the server.
  16. You're done.  To test, click CONNECT and try to access something from your home network.

I use mine for my nightly backups of pictures and videos I've taken on my phone to my NextCloud installation that I don't want publicly accessible.


If you run into problems during the install, stopping and removing all the related docker images will be useful:

  • sudo docker stop shadowbox;sudo docker rm shadowbox;sudo docker stop watchtower;sudo docker rm watchtower


Useful links:

2017-07-25

Your Brain, a Temple or Sacred Place


I was reading the article How to consume News a couple of mornings ago, and the last point must have really resonated with me because my mind wondered on the drive to work.  The last point is:

■ Most of all, be kind and respectful to your brain. Treat it like you would treat your home or a temple or a sacred place. Control what goes in there with extreme prejudice. You have finite mental horsepower. Use it on things that are worth it.

So I thought, treat your mind like a temple, that's kind of like treating your body as a temple, where someone takes care of their body and what they put into it.  Eating healthy, or watching what you eat can have significant benefits.  Over the past couple of decades there has been a huge push towards eating healthy.  The opposite, junk food, and eating it regularly negatively affects your body and overall performance, just watch Super Size Me.  So junk food is bad for you and your body, is there a parallel for your brain and if so what would it be?

Let's think about junk food for a moment.  It generally has high amounts of sugar and/or fat, and our body craves it because it's high in energy.  Let's focus on sugar, where we get a burst of energy, a temporary high, followed by a low.  That can start a vicious cycle of highs and lows, including mood swings.

Can we make parallels between junk food and information and/or news?  Do we get the same highs and lows from fast breaking news?  The latest terror threat, scandal, etc.  Even the urge to look at an accident while you drive by somewhat seems to fit.  In some cases the high, adrenaline, is longer than a sugar high, but it is temporary.  We gossip about the latest thing, but then in a day or two that news is old, forgotten and didn't really benefit us in the long term.  And where do reality TV shows fit, immersed in someone else's trumped up and exaggerated reality instead of your own, or to escape your own (reminds me of an episode of Black Mirror).  We wasted a bunch of time and energy absorbing this unimportant information, and then we move onto the next thing.

What about our psyche or brains?  Negative news dominates in this day and age, is this similar to how junk food once dominated our diets.  Will we learn that this is unhealthy, and that we should be controlling/monitoring what our eyes and ears see and hear, how long will this take for us to learn.

Assuming there such a thing as junk information or junk news, we should be trying to avoid it.  Our brains can only handle a certain amount of information, so what if we're bombarding them with garbage.  Should we be trying to consume healthy information?  Will there be a push in society to control what gets to our eyes and ears just like we do our mouths?

2017-05-01

Adding Feature Switches to your Django Project in 10 Minutes

The first time I heard about feature switches was at a Winnipeg Agile Group meeting a year or two ago.  They peaked my interest immediately and seemed like a must when trying to practice DevOps.

I wanted to try them out on my own, so I did some investigating.  I have a Django hobby project and figured it would be a perfect target for trying out feature switches.  I started by writing down some of the features I though would be useful:

  • Stored in a database
  • Support changes on-the-fly
  • Global enable/disable
  • Must support multiple conditions
    • client IP address
    • user
    • group
    • date/time
  • Use them in several scenarios:
    • code (models.py, views.py in Django)
    • templates (Django)
    • entire projects (urls.py in Django)
Since Django has a lot of open source projects, I decided to do a Google search to see what's available.  I found a comparison with a bunch of old projects (+1 years since updated) that I avoided because they're likely not being maintained anymore, and decided to try out gargoyle since it had most (if not all) of the features I thought would be useful.  The original project is deprecated, however Yplan forked it and is actively maintaining the project.  So I downloaded the latest release and manually added it to my project.  I followed their suggestion and also added nexus, then later found I needed to add the dependencies modeldict and jsonfield (link to the project that worked).

However, when I logged in using my admin user and tried to add my first feature switch in the nexus UI, I repeatedly got the error "Enter valid JSON".  I tried a couple of different syntaxes thinking maybe it's expecting quotes, or doesn't support certain characters.  No luck.

Then I found a compatibility post, and figured maybe my jsonfield project is incompatible.  So I searched for a different version.  Fortunately the next version I tried fixed the problem, and I was off to the races.  I had my first feature switch saved in the database.

Next up, add some code that uses it.  Based on the really good gargoyle documentation, I made a slight modification to one of my views and the corresponding template.  Loading the page I found my feature switch looked like it was working.  To verify I toggled the feature switch back and forth in the nexus UI and refreshed the page each time.

Done.  That was too easy.

Please contact me if you have any questions, or are interested in the post and what I do.  


Below are most of the technical details:

def index(request):
    if (gargoyle.is_active('feature_key', request)):
        switched_variable = 'enabled'
    else:
        switched_variable = 'disabled'
    return render(request, 'project/index.html', {'switched_variable': switched_variable })
  • template additions:
   {% load gargoyle_tags %}
    {% ifswitch feature_key %}
        <DIV>SWITCH: ENABLED</DIV>
    {% else %}
        <DIV>SWITCH: DISABLED</DIV>
    {% endifswitch %}
    <DIV>Switch:{{ switched_variable }}</DIV>

2017-01-03

Liferay 6.1 - connecting to LDAP server

I've been working on a portal project recently using Liferay.  After getting Liferay installed on a local VM I was tasked with connecting it to an LDAP server for user authentication and importing groups for permissions.

Adding an LDAP server is simple.  Liferay has buttons for testing the connection, user import (with preview) and group import (with preview) that are extremely helpful.  Obviously, all of your settings will depend on the LDAP server you're connecting to.  In my case I didn't need to change any of the user and group default settings.  I did have to change the defaults for the connection.

I  found that using the Liferay control panel was the best way to connect to the LDAP server and determine the settings you need.  What you do with the settings after that really depends on your situation.  I'm a developer, and the project required extensive Liferay customizations.  Therefore, I moved the settings into an Ext plugin which we managed in SVN and deployed to multiple environments.  Alternatively, each environment can be manually however you may find this to be difficult to manage.
I wanted my own LDAP server because I needed to create my own set of users, groups, and have complete control over the structure.
I installed openLDAP locally and connected to it using jxplorer.  Then I created a bunch of groups and users by importing an ldif file.  My database looked good, and connecting/navigating it using jxplorer worked fine.  
To help verify/debug connecting to LDAP servers I used jxplorer, which is free and available for all of the common OSs.
 
The Technical Details

In my case the LDAP server is a test server, and was really easy to connect.  The hardest part was finding where the LDAP settings are located in the Liferay control panel. To get to the LDAP settings:
  1. Login to Liferay (http://localhost:8080, in my case test@liferay.com:test)
  2. Navigate to the control panel (Go To->Control Panel)
  3. Then under Portal in the left navigation, go to Authentication->LDAP

The options for LDAP are pretty self-explanatory:
  • Enabled - enable LDAP authentication
  • Required - require LDAP authentication (the only exception I'm aware of is the default admin user created when first logging into Liferay)
  • Import Enabled - import LDAP users and groups into Liferay
  • Export Enabled - export user and group changes made in Liferay to LDAP
  • Use LDAP Password Policy - I recommend selecting this, otherwise when users first login they will be forced to change their password
There are some useful LDAP options (for Liferay 6.1) that you can add to your portal-ext.properties file as well.  In my case I'm using the glassfish hosted version of Liferay 6.1, so my portal-ext.properties file is in liferay-portal-6.1.20-ee-ga2/glassfish-3.1.2/domains/domain1/applications/liferay-portal/WEB-INF/classes.  The following are some of the more useful settings that I used.
  • ldap.import.on.startup=false
  • ldap.import.interval=10
  • ldap.import.create.role.per.group = true
    • This is to create a role for each group, and add each group member to that role
    • I use this because all our permissions are role based
  • ldap.import.enabled=true
  • ldap.export.enabled=true
  • ldap.import.method=group
    • Only import users that are part of groups
    • The other option is user, i.e. only import groups that have users

In my case I needed to fill out my Base DN to import the users (dc=example,dc=com).
  •  Connection
    • Base Provider URL - ldap://192.168.1.55:389
    • Base DN - dc=example,dc=com
    • Principal - cn=Manager,dc=example,dc=com
    • Credentials - *****
  • Users
    • All defaults
  • Groups
    • All defaults
  • Export
    • Users DN - dc=example,dc=com
    • User Default Object Classes - top,inetOrgPerson,person
    • Groups DN - dc=example,dc=com
    • Group Default Object Classes - top,groupOfUniqueNames

Minor Issues I Encountered

The first issue you might run into is a certificate problem (if using ldaps).  Logging in will simply fail, and there won't be anything in the logs either.  To fix the problem you have to add the LDAP certificate to the Java cacerts store.

Then, Liferay could only connect.  Trying to import users and groups failed.  Eventually I determined that the problem was because I hadn't added the user I use to login to LDAP to the LDAP database.  I'm not sure why Liferay requires this and jxplorer doesn't, or for that matter why the user isn't automatically added to the database during installation/setup, but it's not.

During the import I ran into an problem.  I had issues (debugged continually restarting Liferay and using the liferay log file which was very time consuming) with screen names.  I simplified all my users so that they only contain alpha-numeric characters for all properties (cn, sn, uid, etc...).

Another possible issue you may run into is how LDAP is storing passwords.  By default Liferay seems to expect plain text, so that's what I'm using on my LDAP server.  Obviously you don't want to do this in production.  However, during development plain text is fine.

LDIF Snippet for Generating Users

Here's a snippet of the LDIF file I imported to setup my users and groups (LDIF->Import file in jxplorer):


dn: cn=u1,ou=users,dc=example,dc=com
cn: u1
sn: u1
objectClass: top
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
uid: u1
ou: users
givenName: u1
title: Mr.
mail: u1@
liferay.com
userpassword: u1

dn: cn=u2,ou=users,dc=example,dc=com
cn: u2
sn: u2
objectClass: top
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
uid: u2
ou: users
givenName: u2
title: Mr.
mail: u2@
liferay.com
userpassword: u2

dn: cn=u3,ou=users,dc=example,dc=com
cn: u3
sn: u3
objectClass: top
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
uid: u3
ou: users
givenName: u3
title: Mr.
mail: u3@liferay.com
userpassword: u3



dn: cn=g1,ou=Groups,dc=example,dc=com
objectClass: top
objectClass: groupOfUniqueNames
cn: g1
description: g1
uniquemember: cn=u1,ou=users,dc=example,dc=com
ou: groups

dn: cn=g2,ou=Groups,dc=example,dc=com
objectClass: top
objectClass: groupOfUniqueNames
cn: g2
description: g2
uniquemember: cn=u2,ou=users,dc=example,dc=com
ou: groups

dn: cn=g3,ou=Groups,dc=example,dc=com
objectClass: top
objectClass: groupOfUniqueNames
cn: g3
description: g3
uniquemember: cn=u3,ou=users,dc=example,dc=com
ou: groups


2016-04-05

Giving an old wireless router (WRT54G) a new life

Over the holiday break, I had some spare time to work on projects around the house.  One of the projects was to give my old wireless router (WRT54G) a new life as a repeater.

I can't say enough about my current wireless router, an Almond+ by Securifi, it works great, it's easy to use and has tons of automation features.  However, it's located at the front of my house and coverage isn't great at the back of my yard.  To solve my 1st world problem, I reconfigured my old WRT54G.

Here's a couple of things to keep in mind during the process:

  • It's good to have separate internet access throughout the process so that if you have to look anything up, you can find a solution quickly.  My smartphone did the trick for me.
  • Be patient, after restarting the WRT54G sometimes the web interface (LuCi) and SSH access were slow to come up (connection lost message, etc...).  Wait a minute and try again
To start, I dusted off my WRT54G and got to work.  I checked out openwrt, and found my router in the OpenWRT Table of Hardware (make sure you pay attention to version numbers listed on the bottom of your router) and read the WRT54G device page.  It's a good idea to read the entire page, but really the most important section is Installing OpenWRT. 

I installed backfire v10.03.1 brcm-2.4 on my v3 router.  It was simple to follow their steps, and I had no problems at all.

Next, because I wanted to extend my existing WLAN, I configured the router to be a wireless client bridge (bridgedclient mode).  You can find the recipe here.

The most complicated part of the process was configuring the router for my WLAN.  Here are a couple of notes to help:
  • You'll need a telnet client and/or SSH client installed on the computer you're using to access the router and configure it
  • The router is running Linux, so I used vi (vi cheat sheet) to edit the files, which is a text editor and is a bit confusing if you're not familiar with it
  • All the configuration files are in /etc/config/
  • The default IP address of the WRT54G is in /etc/config/network, and was set to 192.168.1.1 so I had to first change my laptop's IP to the same network so that I could access the router (via a wired Ethernet connection).
  • To change my laptop's IP:
    • Disable DHCP (control panel -> network connections -> lan connection properties -> TCP/IP v4 properties, set the IP to 192.168.1.200 and default gateway to 192.168.1.1
  • Make sure re-enable DHCP on your laptop so that you can access the WRT54G after it is restarted

So that I don't forget the static IP of my WRT54G, I wrote it on the bottom of the device.  Then I plugged it in near the back of my house, and now I have extended WLAN coverage.

2016-02-27

Why is the JVM on my Linux Server using so much CPU?

Once in a while the CPU usage on one of our development servers rails and hits 100% and never lets up.  A couple of times it actually hit 200% (2 threads burning 2 cores at 100%) or more before we noticed.  These are not good situations, and something you want to get to the bottom of and fix before it starts happening in your production environment.
The lazy thing to do is restart the entire server, or restart the process that's railed.  But this is just ignoring the problem.  Avoidance is what you should be doing.  Here's how you get to the bottom of what's going on:
1. Get the PID causing the problem, run top to get the list of processes using the most CPU, it'll be the first entry.  As you can probably guess, it was my JVM.
2. Get the IDs of the threads in the process causing the problem (my PID was 2089): top -H -p 2089
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
11405 liferay   20   0 11.7g 6.8g  14m R 99.4 88.5  23074:07 java
10000 liferay   20   0 11.7g 6.8g  14m R 99.4 88.5  23079:00 java
 3390 liferay   20   0 11.7g 6.8g  14m S  3.9 88.5  22:42.06 java
 2089 liferay   20   0 11.7g 6.8g  14m S  0.0 88.5   0:00.02 java
 2098 liferay   20   0 11.7g 6.8g  14m S  0.0 88.5   0:01.85 java
 2111 liferay   20   0 11.7g 6.8g  14m S  0.0 88.5   3:15.03 java
 2112 liferay   20   0 11.7g 6.8g  14m S  0.0 88.5   3:11.32 java

From the top listing, it's easy to see the first two threads are the problem, 11405 and 10000 are using almost 100% of the CPU each.  Convert the thread IDs to hex for step 4.
11405 -> 0x2C8D
10000 -> 0x2710

3. Take a thread dump of your JVM: /usr/java/jdk1.6.10/bin/jstack 2089 > /tmp/td.log
4. Search your thread dump for the threads causing your problem (found in step 2).  For example, I found the following:
"pool-400-thread-53" prio=10 tid=0x00007fde4801efd0 nid=0x2c8d runnable [0x00007fdde3ba4000]
   java.lang.Thread.State: RUNNABLE
        at java.util.HashMap.getEntry(HashMap.java:347)
        at java.util.HashMap.containsKey(HashMap.java:335)
        at java.util.HashSet.contains(HashSet.java:184)
        at com.vaadin.ui.CustomTable.unregisterPropertiesAndComponents(CustomTable.java:2322)
        at com.vaadin.ui.CustomTable.getVisibleCellsNoCache(CustomTable.java:2219)
        ...
Looking through the stack trace for 0x2c8d, I was eventually able to find a custom class and a line number that allowed me to narrow down the source of the problem.
Finding the general area of the problem is usually the easy part.  The hard part is eliminating the source of the problem, which I'll leave up to you.
Good luck.

2016-01-25

Beware of Liferay on a GlassFish Application Server

If you're using Liferay to build a portal or website, I highly recommend using Liferay with bundled Tomcat.  Learn from other's headaches, it's not worth the trouble of trying to use something other than Tomcat.

Originally, we were trying to use GlassFish 3.x.x because we have a support contract.  We got our environments up and running reasonably quickly.  Our environments were the following:

  • Local Dev (Windows 7) - Tomcat7 (bundled Liferay Developer Studio), MySQL V5 DB
  • Dev (Ubuntu VM) - Liferay 6.1.2 deployed to GlassFish , MySQL V5 DB
  • Test (RHEL) - Liferay 6.1.2 deployed to GlassFish , Oracle 10G
  • QA (RHEL) - Liferay 6.1.2 deployed to GlassFish, Oracle 10G
  • Production (RHEL) - Liferay 6.1.2 deployed GlassFish, Oracle 10G

Once we started doing significant work, developing portlets and customizing Liferay (hooks, themes, ext plugin, etc...) we started running into problems.

I had difficulties migrating data between environments, which was important early in development.  While trying to migrate the database from one environment to another (Control Panel -> Server -> Administration -> Data Migration), I repeatedly got errors and the migration failed.  I determined that migrating from one environment to another never worked when using Liferay deployed to GlassFish.  The migrations always failed with a null pointer exception.  Analysing the error in the logs and a Google search didn't lead to a solution to the problem.

However, I did eventually find a workaround for migrating data between environments, but it wasn't practical due to our network configuration (Firewalls blocking traffic between certain environments).  The workaround was to connect my Local Dev environment, by temporarily modifying my portal-ext.properties file, to the source database for the data migration.  Basically I found that the version of Liferay bundled with Tomcat was the only Liferay installation able to perform the data migration successfully.

We did encounter other problems using GlassFish as well.  We put a lot of effort into clustering Liferay deployed to GlassFish without success.  After scouring the Liferay forums and discussing our options with Liferay support we decided to give up on GlassFish and use Tomcat instead.

After switching to Tomcat, we had clustering working with minimal effort and our data migration issues were solved.  In hindsight we would have saved a lot of time and effort by simply using Tomcat from the start.  It's important to note as well, that we haven't had any stability/reliability issues due to Tomcat, and have almost been live for 2 years. 

2015-05-04

Polishing your Portal with a High-Load Page

When your servers become overloaded with requests, they're unable to your end users with the experience you desire.  It's important in these situations to fail gracefully so that your end users (customers, employees or whomever it may be) don't become frustrated with your site.  We wanted to provide a customized high-load page so that our site:

  • Fails gracefully for users trying to connect, and minimally affects users who are already connected
  • Looks a more polished and professional
  • Is guaranteed to provide a known QoS to our customers

The portal we're customizing is based on Liferay.  In our case, we didn't like the error page that Liferay provides by default when using the session.max.allowed property, so we decided to do some customizations.

To do so we added a redirect to a high load page based on the user session count.  The high load page is an extremely simple page.  We're using static HTML served by Tomcat, and we made it as light weight as possible.  Serving the page with Apache HTTP would be better, however we don't have the option to do so in our system.

There were 4 steps to adding this page:

1. Add custom properties to portal-ext.properties file

2. Create a class that tracks the session count
 - Use a listener to track the number of sessions , increasing count on creation and decreasing count on destroy
 - There are several examples of doing this on the web, i.e.
 - http://tomcat-configure.blogspot.ca/2009/01/tomcat-session-listener-example.html
 - The listener must be added to the web.xml in your Liferay ext plugin

3. Check session count in all desired classes that extend HttpServlet
  - Must be done in the Liferay ext plugin, follow proper ext patterns explained at www.liferay.com
  - If too high (above threshold in portal-ext.properties), redirect to low load page and invalidate session
    - Redirection page should be in portal-ext.properties to allow it to be reconfigured without recompiling the code (and redeploying the ext plugin)

4.  Create a very simple target page that will result in minimal server load
 - In our case I just created a directory in webapps (using tomcat) with an index.html file
 - index.html contained simple message explaining we're experiencing higher than normal load, please re-visit the site later


2015-02-13

Automatically Email Zigbee/Z-wave Sensor Changes from the Almond+ WiFi Router

Disclaimer: I do not work for Securifi

Last year I backed the Almond+ WiFi router on Kickstarter (Securifi).  After a couple of delays which weren't entirely unexpected from this type of project, I received it in the mail.  Since then, it has been working quite well.

Before delivering the Almond+, Securifi sent out an option for backers to order sensors from them.  Since they were ~$30 each, I figured I would get a couple and try them out.  I ordered a flood sensor, AC switch, window/door switch and a motion sensor.

Securifi has a cloud service that allows you to remotely connect to your router, monitor sensors, etc...  They're working on pushing sensor statuses to a mobile app you install on your smartphone, however last I checked they have some bugs they're still fixing.  There are several people waiting patiently for this functionality, including myself.  I also decided to quickly code a poller.

Since you can get sensor statuses from the routers web interface, I decided to write a python polling script that will scrape the sensor values from your router and send an email whenever they change.  I threw the script together pretty quickly, so it's not the greatest code but it works.

I setup the script as a service that automatically runs on startup on a computer running Ubuntu Linux.

A couple of quick notes:
  • Update all the xxxx in the Python source code as appropriate
  • You may have to tweak ALMOND_URL for different versions of firmware
    • I have version Software Version : AP2-R070-L009-W016-ZW016-ZB005
  • I did have issues with the gmail password, and had to follow some steps found at the accounts website (see comment above variable) to setup the application
  • With a couple of changes, I'm sure you can get it working with email services other than gmail, or if you're more ambitious change it entirely to use some other communication (twitter, ifttt would be very powerful, etc...)
  • I added a bit of code to interpret the status of the sensors I have, I haven't tried it with any other sensors but I don't see why they wouldn't work
    • Custom code can easily be added for other sensors to make their status more human readable
Click here for the Python source code.

Post any questions, suggestions, and changes/additions you make in the comments section below.

2013-10-07

How do you deploy multiple versions of the same portlet in Liferay?

While developing a portal site, it can be very helpful to support deploying multiple versions of the same portlet.  Some of the reasons that our development team have encountered include:

  • Helping to debug issues that are introduced in new versions
  • Comparing functionality and performance between two portlets, while keeping everything else equal
  • Comparing functionality and performance between two portlets, while keeping everything else equal
  • When a portlet is used multiple times on a single site, it can be advantageous to use multiple versions of the same portlet so that all dependent portlets don't have to be updated when new features are added to the portlet that is used multiple times
  • In Liferay, JAR files are not cleaned up when a portlet is redeployed.  Therefore, if the JAR files a portlet uses are updated and it is redeployed, both the original JAR files and new JAR files will be in the lib directory.  This can cause issues if the new JAR files contain different versions of classes.
    • We have run into this situation a couple of times, and it leads to confusing and unexpected results
To properly version a portlet, you need to do two things which we achieved by adding version numbers to both:
  1. Make the directory the war file is deployed to unique so that Liferay treats them as separate portlets (in the webapps directory)
  2. Make the name of the portlet that shows up in Liferay's Add menu unique so that you can control the version of the portlet that is added to a page, and later on determine which version of the portlet is on each page
Controlling webapps directory

Through experimentation, I found that the webapps directory is based on the WAR filename.  The exact WAR filename is used as the name of the webapps directory, except when the WAR filename contains the string "-portlet".  When the WAR filename contains specific character sequences, everything after them is ignored.  The character sequences that I know about are: -portlet; -hook; -ext.  Here are a couple of examples:

WAR Filename
webapps directory
calendar_1.0.3.1.war
calendar_1.0.3.1/
calendar_1_0_4_0.war
calendar_1_0_4_0/
myportlet-portletAA.war
myportlet-portlet/
crazystuff-ext.war
crazystuff-ext/
myaccount-hook12-production.war
myaccount-hook/

Controlling the portlet name (in Liferay)

The first thing that needs to be done is to make the portlet ID unique so that Liferay can track it.  The portlet id is in liferay-display.xml.  I simply concatenate the version number onto the end of the portlet ID.

Next is to update the portlet name.  The name must be updated and kept consistent in portlet.xml and liferay-portlet.xml.  To be consistent, I simply concatenate the version number to the end of the portlet name.

One thing to keep in mind is that there is a known bug in Liferay, where an exception is thrown if the portlet name has a hyphen ('-') in it.  We also found issues with periods ('.') in the name and in the WAR filename.  So what we do is avoid using either of these characters (as well as spaces), and replace them with underscores ('_').

Maven


In our case, we are using Maven.  So to simplify portlet versioning, we keep the version number in the POM file and variables throughout the other files where necessary.  The variables are automatically replaced with the version number when the WAR file is generated.

In the POM file we setup the WAR filename to be ${pom.name}_${pom.version}.war to minimize the number of changes required when changing the name and version of the portlet or hook.  Note that Maven can't be used with ext plugins.

2013-08-05

Liferay - Multi-Stage Development and Data Management

Our development environments and data migration strategy is something that has evolved during the project, and will continue to evolve during development.  Process and design improvements are encouraged and investigated in an attempt to optimize the end product.

When multiple developers are involved in a large project, multi-staging is extremely important.  It allows developers to work simultaneously and independently, without affecting each other.  It also allows stability and loss testing along with a bunch of advantages. The number of stages required should depend on several things (this is obviously not a complete list):
  • Size of the project
  • Complexity
  • Number of developers
Two stages is the minimum required for any project, one for development and one for production.  However, it's highly recommended to have a Test environment between Development and Production.  The Test environment should be identical, or at least as close to the Production development as possible to minimize risk when deploying updates to Production.

In our situation we have three stages, which are:
  • Development
  • Test
  • Production
Furthermore, we are currently debating adding a fourth stage, QA, that would be always kept identical to the Production environment.

To meet our requirements, the Production environment is designed to be highly available with session replication.  Each stage incrementally becomes more similar to the Production environment.  The incremental changes spread out the issues that are due to environment variations, easing debugging.  Each environment is designed specifically for a purpose. 

Initial development is performed on a local desktop or laptop, which is not listed above.  The local environment is the most flexible, easily restarted and best for independent development and debugging.  Each developer has a complete environment running locally on their desktop/laptop.  This environment, which is used for rapid development, initial integration and testing by the developer, debugging, etc...  It gives developers the maximum freedom to work without affecting each other, which is very important near the start of the project because restarting services is quite common.  Locally we're developing using Liferay Developer Studio on Microsoft Windows.

We're using Development for initial integration between developers and testing.  It is running on RHEL.  The Test environment adds session replication, is located in a DMZ with public access and uses an Oracle database.  The differences in the development and great environment increase reliability and performance.  They also make the test environment extremely similar to the Production environment.

Data migration between environments quickly became important to synchronize
configurations and reduce overall effort.  We use built in features of Liferay to migrate documents, content and configurations.

To migrate web content as well as documents, we export and import LAR (Liferay ARchive) files. They're easy to use, however sometimes we notice issues with improper migration and have to repeat the export and/or import.  Issues we've encountered include missing content and permissions.

You can use staging to migrate pages and other configuration, however we we are using the database migration (Control Panel -> Server Administration -> Data Migration)  in Liferay. Data migration is a bit more work to use, but right now we aren't synchronizing individual pages -- which is the main advantage of staging -- we generally want to synchronize everything.

The headaches that go along with database migration are having to initialize the target database (shutting down the instance of Liferay that's using the database, and deleting all tables within the database) and then restarting both Liferay instances.  It is much more powerful though, allowing us to migrate from any environment to another. We find that it is extremely useful to migrate from Development or Test to our local environment during integration and testing.  It will also be important to migrate from production to test, development or locally when debugging and trying to recreate problems that occur in production.

To aid with data migration we switched from using the default hypersonic database to using MySQL in all the environments before test.  The Test and Production environments are using an Oracle database.  Using mysql was done for several reasons. 
  1. Allows database backups and quick restores because the database does sometimes become corrupt. We have nightly backups of our local and development databases.
  2. Allows database migration to and from any environment
  3. Allows direct access to the database for debugging and clearing lock records (Lock_ table)
Originally we were hosting Liferay on Glassfish, but we ran into several issues including session replication and data migration. Since then we have switched to using tomcat as our application server, and have not had any application server problems since.

So for now, we have a pretty complete development strategy with respect to staging, backups and data migration. We did not arrive at this state right away. Several of the decisions were made during development in an attempt to streamline processes, reduce effort, improve efficiency and end up with a more easily maintainable end product; something that our team is constantly working to achieve.

2013-06-08

RSS - Replacing Google Reader with ifttt and email

I used to be a Google Reader user, and used it religiously.  I used the web interface, and had NewsRob on my phone.  It was great.  Where ever I was, I had access to all my news feeds, and never missed an article.  I had my feeds nicely organized by subject, and kept on top of them.  Checking my feeds was a regular part of my day, quickly skimming titles for articles I was interested in and archiving items using other tools so that I could read them later when I had the time.

When I heard that Google Reader was getting axed I wasn't sure what I'd change to.  I tried a couple of alternatives that people posted (ex. Lifehacker's Five Best Google Reader Alternatives).  Specifically Feedly and NetVibes, but they weren't quite what I was looking for.  NetVibes is good, but I had periodic synchronization issues (entire RSS feeds would become unread) and it only syncs 100 items at a time, so it isn't that useful offline.

I continued to use NetVibes for a month or so, and then it clicked.  I was interacting with feeds a lot like email (and in fact Google started using the same UI for both Google Reader and Gmail).  So I figured why not just develop something that will convert RSS feed items into emails, then just use whatever email client I want to manage the articles.  Some of the advantage of managing feed items as emails is that:
  • Email addresses are free
  • Email is sticking around and commonly used
  • There are numerous email clients
  • A lot of people are working on improving email clients and managing emails
I had developing a daemon that read RSS feeds and generated emails based on them on my to do list for a while.  I knew it wouldn't be too difficult to do, because I had developed daemons that generated emails previously (I wrote a simple one in Python that checked the stock status of the Nexus 4 every 5 minutes and sent me an email when it changed).

The much easier solution, which I came to before wasting development work, was to use ifttt.  I already use it to forward specific G+ posts to Yammer, Twitter, etc... and it works great.

I created recipes for each RSS feed I follow.  I configured the recipes so that they're easy to manage
within ifttt and the emails are easily manageable.  Here are the steps I followed to get setup:
  • Sign up for a gmail account
  • Sign up for an ifttt account
  • Create a recipe for each RSS feed
    • Add RSS tag to recipe description (#RSS)
    • Add RSS feed name to recipe description
    • Trigger: RSS feed (need the URL)
    • Action: Send an email, prefix the subject with an acronym for the RSS feed
The prefixes in the subject allow you to quickly look through items and determine the feed they came from.  You can also use the prefix to sort/filter incoming emails and redirect them to folders (most email) or add the appropriate labels (gmail).



Archiving items for reading later or reference is very easy to do with emails as well, which is another nice benefit.

Some final notes:
  • You can use an existing email address.  If you do, I suggest adding something in the subject line of the email that allows you to create a custom filter to separate the feed items from normal emails.
  • Keep the prefixed used in the subject short so that it doesn't dominate the subject line space when looking at the emails in your email client (especially when using a small screen like your phone)