Monday, January 23, 2023

A Systems Engineering Attempt to Analyze the Linux Block Layer with BPF Compiler Collection (BCC) Toolkit and Static Tracepoints

Originally posted on medium on Jan 16th, 2023.

Systems Engineering

NASA defines Systems Engineering as follows:

At NASA, “systems engineering” is defined as a methodical, multi-disciplinary approach for the design, realization, technical management, operations, and retirement of a system. A “system” is the combination of elements that function together to produce the capability required to meet a need. The elements include all hardware, software, equipment, facilities, personnel, processes, and procedures needed for this purpose; that is, all things required to produce system-level results. The results include system-level qualities, properties, characteristics, functions, behavior, and performance. The value added by the system as a whole, beyond that contributed independently by the parts, is primarily created by the relationship among the parts; that is, how they are interconnected. It is a way of looking at the “big picture” when making technical decisions. It is a way of achieving stakeholder functional, physical, and operational performance requirements in the intended use environment over the planned life of the system within cost, schedule, and other constraints. It is a methodology that supports the containment of the life cycle cost of a system. In other words, systems engineering is a logical way of thinking.

My key takeaways from the block of text above could be reduced to phrases of “combination of elements” and “interconnection of parts”. In fact, if I were asked to describe systems engineering, my short attempt would be:

understanding how components of a system interact with each other and affect the system as a whole.

Abstract definitions do not really impress me so I am going to continue with a real life example to set the tone of this article. In the 60s, during the second test flight of TSR-2, British Aircraft Corporation’s Cold War era interdictor aircraft, oscillations from an engine fuel pump traveled through fuselage and caused temporary loss of vision for the pilot due to the resonant frequency of the human eye ball. The pilot had to adjust the throttle to control these vibrations and maintain vision during flight.

As an individual with a mechanical engineering degree and a bias to all things mechanical, I find this historical anecdote as impressive as an example can get to demonstrate the inter-connectivity between seemingly distant parts of a system. Hoping that I was able to explain my mode of thinking influencing my technical decisions, I can now focus on the actual topic of this article.

Goal

As far as software systems are concerned, the Linux kernel is as complex and its components are as interconnected as it gets. In this huge ecosystem, the block layer, with its components stacked tightly on top of each other, is a prime target for systems engineering. To illustrate my point, here is a simple question: how does a stripe size configuration change in dm-stripe ripple up or down the block stack? To be able to answer a question like this, we need to start with a more fundamental question: can we track an IO request all the way through a stack across multiple component types and monitor and collect performance statistics along the way. In essence, can we peel each layer individually to have a peek inside the system?

The first obvious response that would follow is whether an approach like this is actually needed or not. One may argue that there is already a wide variety of tools that provide the same visibility into the kernel. The iostats tool makes the accounting data available for each device in the block stack. There is blktrace which is specifically intended for the block layer in terms of tracing. BPF/BCC tools provide visibility into disk operations, bio processing, and more. There are indeed many alternatives out there.

The counter argument is that while these tools are very effective, I think they represent approaches on opposing ends of the spectrum. Some generic performance tools, such as iostats, do not differentiate between io scheduler and driver (scsi, nvme, etc.) performance numbers for request processing (sar toolset does). On the other end of the spectrum, we also have access to tools such as SystemTap and BPF that allow us to delve deep into the kernel. However, tools under the latter category tend to get pretty focused targeting specific layers and functionality. The end result is that the deeper we go, the harder it gets to maintain a view of the overall picture.

The above arguments are not intended to portray these tools as limited and/or flawed in any way. Uses of these tools almost always align with the intended goal. On the other hand, there could be a need in having a solution that is generic enough to provide coverage across distinct components in a stack but also focused enough to provide detailed information on each distinct layer to perform proper systems engineering on that stack. As a systems engineer who has to evaluate and propose system designs, I think the ability to have a flexible method to observe the interactions of each layer and understand the behavior of a system as a whole would be useful. Consequently, I am convinced there is value in targeting somewhere middle in the spectrum in this specific case, and, therefore, my initial goal will be to implement a prototype as an attempt to demonstrate my approach and, also, to understand how effectively I can leverage BPF and static tracepoints against a complex problem like this.

The second part of the answer to the original question is whether this proposed approach is actually feasible or not. The tracing infrastructure built into the kernel is impressive and there is a variety of tools built on top of this ecosystem to extract the data required. Out of these tools, BPF Compiler Collection (BCC) with its Python front end offers all the features and flexibility required to implement this design. The BPF backend provides all the necessary functions to collect internal data, while the Python frontend provides the dynamic capabilities needed to create a generic tool. I can argue, then, that this design idea seems feasible, at least in theory at this point, albeit with certain limitations that I will elaborate on later.

Design

This section will explain the evaluation process behind the proposed design.

Input

Deciding on a suitable input mechanism to collect data is the first decision that has to be made. In this context, input mechanisms refer to the available list of different kernel tracing technologies available for use. There are multiple alternatives, such as kprobes, kfuncs, static tracing points, etc. that can be utilized for this purpose and the primary factor in selecting one to satisfy our goals depends on the desired level of exposure to internal kernel structures.

The Linux kernel is under constant development. Utilizing tracing technologies such as kprobes or kfuncs expose external clients to internal structures which are subject to change. This may not necessarily be an issue for clients that target specific components. Change can still be managed even if that means a somewhat tightly coupled implementation for the client. This reality clearly manifests itself in some BCC scripts where support for a specific feature is checked through a reflection mechanism (if I could use that terminology in this context) before enabling that respective feature. However, for a design that is intended to be generic across a number of components, such as the device mapper implementations that are currently available, maintenance is a major concern.

There is no escape from change. But, the intention here is to make it as manageable as possible. This is where static tracepoints shine. They are not immune to change, but by looking at their implementation, naming conventions, and so on, we can argue that they constitute a somewhat standardized instrumentation interface into the kernel across different components. This, by itself, makes static tracepoints an appropriate mechanism, at least in theory, to keep the tight coupling tendencies of our client code under control.

Exclusive utilization of static tracepoints do imply certain limitations for clients, however. First problem is the placement of these tracepoints in the kernel code. They are called static for a reason and there is a side effect due to that inherent characteristic: client code is bound by these placement decisions. Unlike kprobes or kfuncs, arbitrary function entry and return points cannot be targeted. To achieve the same effect, multiple static tracepoints must be utilized in combination. This means that their placement in the code are critical for accurate resolution and may not be always ideal. On the other hand, these decisions are made by kernel contributors who have the most knowledge about the component in question, so, while this is a limiting factor, it is not a major concern.

Second, the amount of information that we can extract is restricted to a predefined set of values (i.e. format) defined for a static tracepoint. This is a double-edged sword. It provides the necessary standardization required across different components and different implementations of those components to support a generic tool. This is an advantage. The disadvantage, however, is that the client is limited to the information provided; that decision is already made and cannot be changed. This can be a major issue requiring additional complexity to solve, if even possible. In fact, I will comment on this issue further later.

Third limitation is code maturity. Tracing output may not be accurate because static tracepoints may not be properly utilized across different components (see dm-crypt related changes committed earlier in 2022). The requirement to use static tracepoints in combination implies that they have dependencies on each other and events must happen in a sequential order. This is not guaranteed. In this case, there is not much one can do. Either a combination of static tracepoints and kprobes/kfuncs needs to be implemented, or a recent kernel version that addresses these issues is needed. It did not take long for me to understand that I had to pick my battles. As a result, a recent kernel version is used for prototyping with the exclusive utilization of static tracepoints.

Output

At this point, we have an idea about the type of inputs we are going to work with. Then, the question about outputs would naturally follow. As common in many block stack tools, throughput related statistics will constitute the integral part of the output. These will include average latency, average size, and throughput numbers, along with merge related data. Split statistics will also be provided to complement merge data. In addition, as it is common, each throughput and latency data will further be grouped under read, write, and discard sub-categories. Similar to fio output, submission and completion pipelines will be treated as different sub-categories.

Test System

In order to prototype this design quickly, I wanted to eliminate as many external constraints and variables as possible from the beginning. Code maturity issues constituted the easiest target so I booted up a Fedora 36 VM running a cutting edge 6.x kernel.

The VM is configured with a /test mount point that lives on a stack that consists of dm-crypt, dm-thin, dm-stripe, and two emulated SATA drives with a single partition on each. This test stack is illustrated in a diagram below (note that the latency axis is not to scale):

Block Stack Diagram
Block Stack Diagram

lsblk output
lsblk output

The VM’s block stack was deliberately designed to be kludgy to put the ideas presented in this document to the test across different components. For example, the single partition on each drive is clearly not needed, but was necessary to demonstrate some of the difficulties that were faced in dealing with unexpected tracing output.

Note that this diagram is not intended to be fully accurate. For example, the dm-stripe component maps IO requests to multiple devices, but the relationship is reduced from one-to-many to one-to-one for simplification purposes in the diagram. In essence, only a single branch of the block stack hierarchy is illustrated.

Code

The code is available under the sysbpf project on Github. Note that the provided code is a prototype implementation. Quality of the BPF code can definitely be improved. For other, non-superficial, issues and limitations related to the implementation, please refer to the subsequent sections.

Deliverable

The tool outputs columnar data periodically which is controlled by the command line interval parameter. Data is categorized into read, write, and discard groups. Under each group, performance stats, which include request count, average request size, average service time, and average bandwidth, are displayed. In addition, submission and completion pipelines are treated separately.

While columnar data is a good starting point, it is not really ideal to work with directly. The appropriate action is to plot that data for analysis.

Read Statistics
Read Statistics

Write Statistics
Write Statistics

Above are two images that group read and write related stats respectively. Discard stats can also be graphed but are not included for the sake of brevity. Each image includes two columns. Except the last row where only merge and split bandwidths are displayed, the left column contains bandwidth graphs and the right one contains latency graphs.

In terms of bandwidth stats, there is not a lot of interesting information except merge and split bandwidth graphs. The slowest layer dictates the bandwidth limit, so, as long as the numbers are consistent across different layers (and add up if there is a striped layer), there is not much to glean besides the maximum bandwidth limit. On the other hand, latency numbers, which are provided for both submission and completion pipelines, are interesting. In each pipeline, the component that contributes most to overall latency can individually be identified and targeted for optimization.

Limitations

Filesystem Coverage

Filesystems are one of the most complicated layers in the block stack. I remember listening to an engineer explaining to his peers during a presentation that it takes ten years to fully mature a filesystem. Therefore, if our goal is to perform proper systems engineering, a complex component like a filesystem sitting on top of the block stack should not be ignored. The question that follows then is about feasibility.

One major problem is how to connect distinct layers for instrumentation. How do you connect a filesystem that works with file maps with lower layers that perform bio processing? This may be possible as long as we manage to map read and write requests for a file to bios and back. In fact, a similar issue manifests itself when we have to instrument across layers that perform bio processing and request processing. Admirably, static tracepoints are implemented in a generic way across bio and request processing layers to handle this issue without too much hassle. On the other hand, filesystems, at least the common ones such as xfs, ext4, btrfs, etc., seem to have specific and namespaced static tracing infrastructures as opposed to generic ones. These filesystems come bundled with a large list of their own dedicated static tracepoints. This is expected given the their complexity and the work required to support them. However, it is a problem in the context of this design. Considering the number of filesystems supported in the linux kernel, dedicated implementations go contrary to the goals stated in this article as they require individual targeting of each implementation and that, by definition, comes with the cost of tight coupling and the elimination of the benefits provided by static tracepoints.

Next, a curios question follows. Device mapper layers, each with its own and unique internal implementation, provide standard external APIs to the kernel to function as part of the block stack. Block drivers follow the same design pattern; they function under the generic block device layer. Filesystems are not much different in this regard; they are exposed via VFS. Why is there no generic tracing mechanism to provide coverage for filesystems through the VFS layer then?

Targeting specific filesystems seemed to me as a deviation from the stated design goals. The lack of filesytem coverage is, therefore, a serious limitation of this design. This may change in the future, but, for now, it does not exist.

BPF

A complex approach, such as this one, built on a relatively limited framework, such as BPF, is a negative feedback mechanism on the size of a program. As a function starts getting larger and larger, droplets of cold sweat start forming on your forehead as you ineluctably hurtle towards that thick wall of stack size limit.

Complex BPF programs are not easy to debug. In this case, sequential dependencies between parts of the code have necessitated the use of a debugging mechanism to sort out bugs. The end result is that sad and utterly bad logging mechanism in the source code. It should serve as an adequate reminder of debugging problems faced during the implementation of this prototype and a warning to those who are planning to jump in a similar rabbit hole.

Lastly, as complexity increases and the limitations mentioned above become more obvious, questioning the current approach to the problem becomes part of the game. Is a monolithic approach a proper solution to the problem? Can the same goals be achieved with multiple and simpler programs? The cost benefit analysis of complex monolithic approaches should be part of the discussion. In this specific case, while there is complexity stemming from the nature of this approach, a considerable chunk of it is also contributed by underlying tracing related limitations that I will describe in the next section.

Tracing

This section will primarily focus on tracing event format and behavior and their impact on the design in terms of accuracy and complexity.

The first limitation is the lack of a unique identification mechanism for each request. In fact, each request can only be identified by its device, sector, and associated flags. This means that repetitive requests that get queued in submission and completion hash maps may overwrite each other. This has an adverse effect on the accuracy of stats.

Second, tracking requests crossing partition layers is involved. The tracing output seems to have a bug containing the partition owner as the target device for the remap operation.
dmcrypt_write/2 3748 [010] 68.318853: block:block_bio_remap: 8,16 WS 29824 + 128 <- (253,1) 55680
dmcrypt_write/2 3748 [010] 68.318854: block:block_bio_remap: 8,16 WS 33920 + 128 <- (8,17) 29824
As can be seen above, there is no subsequent bio complete event for the first remap event and it is actually followed directly by a new remap event. This divergent behavior for partitions requires additional logic to identify the correct target partition device for the initial remap event. Needless to say, significant complexity can be eliminated if block layer components behave as standard as possible, at least, from a tracing perspective.

Third, static tracepoint formats are not always adequate and require additional logic to work around their limitations. Backmerge events constitute the best example of this limitation.
dmcrypt_write/2 3748 [010] 68.318893: block:block_bio_backmerge: 8,16 WS 34048 + 128 [dmcrypt_write/2]
Merge target information is not provided in the backmerge event format. This requires an additional hash map that tracks previous requests in the pipeline for the sole purpose of identifying the target. Again, this complexity can be completely eliminated if event formats are improved in these cases. I believe a static tracepoint event format should, at minimum, provide a sufficient level of information to describe the respective event in its entirety, so that redundant external constructs to track other entities can be completely eliminated.

Fourth, the behavior of a request crossing up and down a stack of block layers is unfortunately not standard. The sequence of events may change due to a variety of factors. As usual, issues are exacerbated by introduced complexity on the client side required due to missing information. The end result is again accuracy issues that compound as requests make their way down and then up the block stack. One can argue that dependency on the order of operations is inherently a requirement dictated by the design itself. That is a correct statement. On the other hand, I believe deviation from standard behavior should at least merit an investigation into its underlying causes and a call for potential improvements if feasible.

Device mapper split events manifest one of the most problematic behaviors under this category.
# Linux test 6.0.16–200.fc36.x86_64 #1 SMP PREEMPT_DYNAMIC Sat Dec 31 16:47:52 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
dmcrypt_write/2 3748 [010] 68.318825: block:block_bio_remap: 253,4 WS 33024 + 512 <- (253,5) 256
dmcrypt_write/2 3748 [010] 68.318828: block:block_bio_queue: 253,4 WS 33024 + 512 [dmcrypt_write/2]
dmcrypt_write/2 3748 [010] 68.318843: block:block_bio_remap: 253,2 WS 55680 + 128 <- (253,4) 33024
dmcrypt_write/2 3748 [010] 68.318846: block:block_split: 253,4 WS 33152 / 33152 [dmcrypt_write/2]
dmcrypt_write/2 3748 [010] 68.318869: block:block_bio_remap: 253,2 WS 55808 + 128 <- (253,4) 33152
dmcrypt_write/2 3748 [010] 68.318871: block:block_split: 253,4 WS 33280 / 33280 [dmcrypt_write/2]
dmcrypt_write/2 3748 [010] 68.318884: block:block_bio_remap: 253,2 WS 55936 + 128 <- (253,4) 33280
dmcrypt_write/2 3748 [010] 68.318886: block:block_split: 253,4 WS 33408 / 33408 [dmcrypt_write/2]
dmcrypt_write/2 3748 [010] 68.318896: block:block_bio_remap: 253,2 WS 56064 + 128 <- (253,4) 33408
swapper 0 [010] 68.319264: block:block_bio_complete: 253,4 WS 33408 + 128 [0]
Note the matching sector and new_sector fields in the split event traces. This means that the split request size cannot be calculated from the difference. The end result is again more complexity to address that lack of information. In the above case, split events are triggered after the first remap. So the upcoming splits have to be inferred from the size difference of request [(253,4) 33024] when it gets remapped to the next layer. The size information from this remap operation is then carried over to the subsequent split events in order to track new requests and collect statistics.

Problems related to device mapper splits do not end there. The first bio never receives a bio complete event. Instead, it is the last split request that receives one. Accuracy issues are again the end result as completion stats are skewed.

Fifth, bio complete event format does not provide any information regarding the next layer. There is no static tracepoint event on the completion pipeline analogous to the remap event, either. This requires more complexity to address in the form of a stack mechanism to track layers. As a request travels down, components are pushed to the stack, as they travel up, components are popped off from the stack. It is not easy to get this right, particularly in cases like splits, and this compounds accuracy problems.

Sixth, there is no bio complete event triggered for the block device itself. The rq complete event, which actually belongs to the driver layer, is available instead. It is subsequently followed by a bio complete event for the upper layer.

Lastly, request flags can be dynamic and may influence behavior. In the case of a request with a flush flag, the original request is split into two distinct ones: first one with the flush flag only and the second one with all flags except the flush flag. In the case of a request with a fua flag, once the flush action takes place, it is removed from list of flags for the request. For these reasons, flush and fua flags have to be excluded when identifying a request.

Implementation

The test system presented in this article showcases only a subset of device mapper layers available. This lack of testing coverage may expose users to bugs. Devices may behave differently due to a variety of reasons and this tool may require additional work and/or fixes to function properly.

On a similar note, loop device coverage is not provided. Even if support is added for loop devices, without filesystem coverage, full instrumentation of the block stack is not possible at this time.

Certain events may not be properly covered as they are hard to trigger and, therefore, test. Frontmerge events constitute a good example in this category. This, coupled with the fact that debugging is hard, is another reminder that users are likely to hit bugs.

While the goal of this exercise was an attempt at systems engineering, it is also obvious that it was also an attempt at complexity, and, by extension, overhead. Although I was not able to discern much by comparing output with other tools, I also wonder how much overhead is actually introduced by simply tracing with a heavy design such as this one.

Improvements

Here is a list of improvements that I would like to see as I continue to iterate on this design:
  • Ability to target a specific block stack branch hierarchy.
  • Ability to target a specific cgroup.
  • Support for filesystem layer tracking.
  • Collection of flush statistics.
  • Integration of the page cache layer and its stats for non-direct operations, if possible.

Feedback

Any feedback from anyone who can test this prototype on different block stack configurations is welcome. I am also interested in hearing about potential improvements to the BPF code in terms of quality and performance.

Sunday, July 26, 2015

Home Monitoring with a Raspberry Pi 2 Model B and ZoneMinder

Overview

I had been thinking about setting up a camera system in my place for a while. My plan was to set up two cameras with motion detection; one to monitor the patio for deliveries and another to monitor the living room in case there was a break in. This plan stayed in the back burner for a long time until my colleague's house got burglarized just a couple of weeks ago. He was in a different state at the time; however, as soon as he received an alarm, the police department was called and one of the intruders got caught right after he left the house. Suddenly, I had a good reason to implement my own system.

My design requirements could be described as simplicity. I just needed two cameras with wireless connectivity to minimize cable clutter. The first camera would be monitoring my living room and needed to be PTZ capable. My idea was to disable monitoring while I was at home and I needed a camera with this capability to provide a visual indication. During the initial setup, I also found an old Logitech webcam which I had completely forgotten about. I decided use that for monitoring my office. In addition, the last thing I wanted was to expose my cameras to the internet so I was also looking for a central control system. At the end, my configuration boiled down to a Raspberry Pi 2 Model B running ZoneMinder controlling a Foscam FI8918W and a Logitech webcam. I still haven't decided on the camera to monitor my patio.

Initial Setup

  • Copy the Raspbian image to your micro SD card. Instructions are at: https://www.raspberrypi.org/documentation/installation/installing-images/README.md
  • Type root and hit enter after powering on your Pi
  • Get the MAC address for your Pi
  • Configure DHCP service on router to assign a static address to your Pi
  • Change hostname by editing /etc/hostname
  • Fix keyboard setup in /etc/default/keyboard
  • Fix timezone
    • rm /etc/localtime
    • ln -s /usr/share/zoneinfo/US/Pacific /etc/localtime
  • Update /etc/profile
    • export TZ=America/Los_Angeles
  • Reboot

Securing the Pi

  • Create a local user
    • groupadd buraks78
    • useradd -g buraks78 -m buraks78
    • passwd buraks78
  • Create an .ssh folder for this user and copy your public key in authorized_keys.
    • su - buraks78
    • mkdir /home/buraks78/.ssh
    • chown buraks78:buraks78 /home/buraks78/.ssh
    • chmod 0700 /home/buraks78/.ssh
    • cat /tmp/id_rsa.pub > /home/buraks78/.ssh/authorized_keys
    • rm /tmp/id_rsa.pub
    • chown buraks78:buraks78 /home/buraks78/.ssh/authorized_keys
  • Add user to sudoers using visudo
    • buraks78 ALL=(ALL) ALL
  • Update sshd configuration in /etc/ssh/sshd_config and restart sshd
    • Protocol 2
    • LoginGraceTime 30
    • PermitRootLogin no
    • PermitEmptyPasswords no
    • PasswordAuthentication no
    • X11Forwarding no
  • Setup iptables (DON'T LOCK YOURSELF OUT!) (This will break aptitude obviously)
    • aptitude install iptables
    • /tmp/iptables.txt
      *filter
      :INPUT DROP [45:2307]
      :FORWARD ACCEPT [0:0]
      :OUTPUT ACCEPT [1571:4260654]
      -A INPUT -p tcp -i lo -j ACCEPT
      -A INPUT -p tcp --dport 22 -j ACCEPT
      -A INPUT -p tcp --dport 80 -j ACCEPT
      -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
      -P INPUT DROP
      -A OUTPUT -o lo -j ACCEPT
      -A OUTPUT -o eth0 --destination X.X.X.X/24 -j ACCEPT
      -A OUTPUT -o eth0 -p tcp --dport 587 -j ACCEPT
      -P OUTPUT DROP
      COMMIT
    • iptables-save > /tmp/iptables.txt
    • iptables-restore < /tmp/iptables.txt
    • iptables-apply
  • Change zm password for MySQL
    • myqsl -u root -p
      • GRANT USAGE ON *.* TO 'zmuser'@'localhost' IDENTIFIED BY 'XXX';
      • FLUSH PRIVILEGES;
    • vi /etc/zm/zm.conf
      • Update ZM_DB_PASS to XXX
    • Restart ZoneMinder
  • Secure Apache installation
    • Implement directory restrictions.
    • TODO: Server SSL setup
    • TODO: Client SSL setup

Component Setup

  • Install ZoneMinder
    • Instructions are here: https://github.com/ZoneMinder/ZoneMinder
    • Well, the version in the Raspian repo is old. Simply check out the master branch and build (I tried to build v1.28.1 and release-1.28.2 and both of them failed)
  • Enable zm.conf for Apache
    • Add the following line to Apache configuration: SetEnv TZ America/Los_Angeles
    • Ensure /etc/profile is updated as described previously
    • Update /usr/bin/zmpkg.pl to add: $ENV{TZ} = America/Los_Angeles;
      • Otherwise filters do not email/message properly
  • Setup Foscam
    • Static dhcp reservation on the router
    • Shut down all unnecessary features like motion detection and alarms
  • Introduce Foscam to ZoneMinder
  • Introduce Logitech webcam to ZoneMinder
    • Add www-data user to video group to video group: usermod -G video www-data
    • Restart ZoneMinder
  • Configure ssmtp for sending emails 
  • Configure ZoneMinder
    • Enable OPT_USE_AUTH and update AUTH_HASH_SECRET
    • Enable OPT_CONTROL
    • Set EVENT_CLOSE_MODE to alarm
    • Enable LOG_DEBUG during setup

Performance Optimization


Automation

  • Implemented a simple cronjob to disable alarms when I am at home and enable them after I leave by pinging my phone IP. The last preset for the Foscam is directed at the ceiling to provide a visual clue that no monitoring is taking place.
#!/bin/bash USER=admin PASS=XXX ping -l 5 -c 5 -q  X.X.X.X &> /dev/null if [ "$?" -eq 0 ] then   # Foscam   zmu -m 1 --noalarm --username $USER --password $PASS   zmcontrol.pl --id 1 --command=presetGoto --preset 8   # Logitech   zmu -m 2 --noalarm --username $USER --password $PASS   logger -t zonemaster "ZoneMinder is now DISARMED" else   # Foscam   zmcontrol.pl --id 1 --command=presetGoto --preset 2   sleep 10   zmu -m 1 --noalarm --username $USER --password $PASS   zmu -m 1 --cancel --username $USER --password $PASS   # Logitech   zmu -m 2 --noalarm --username $USER --password $PASS   zmu -m 2 --cancel --username $USER --password $PASS   logger -t zonemaster "ZoneMinder is now ARMED" fi

Conclusion

  • First of all, stick to the defaults. I can not stand Debian... So, I installed CentOS on the Pi but quickly realized how deep the rabbit hole went after seeing how many packages I had to build to get ZoneMinder working. Just put down the CentOS image and step back slowly. It is not worth it.
  • These cameras all suck... The ones that don't are extremely expensive so better get used to the crap.
  • While researching for this project, I thought about using power line adapters instead of wireless. That idea was thrown to the retarded bin when I realized I needed an additional PoE injector. There is not even one power line adapter with built-in PoE injection capability. At least, I could not find one on Amazon.
  • Central control unit... I even thought about getting an ITX motherboard for processing power. A single Raspberry Pi 2 Model B is not going to cut it if you have a lot of cameras. Horizontal scaling would be nice. I am still reading/learning about Raspberry Pi clustering.
  • About micro SD lifespan. Shut down your Raspberry, remove the card, and make a copy of it using dd. You will need it.






Friday, July 4, 2014

Automate SSH Login, TMUX Session Creation, and Window Setup

Modern Time Technology

That was it... I was finally fed up with amount of time I was spending to SSH into each host manually. I was repeating the same procedure probably hundreds of times every day... Typing the ssh command and the host name, waiting for the initial prompt, entering my password, typing the sudo -i command to get root access, and entering my password again. Just as expected from a modern time worker. But wait, there was more to deal with...

Working with a lot of servers, I need to use my favorite terminal emulator to organize and manage the number of sessions. This means that every window has to be properly named and assigned to a specific session. So before logging into each server, I had to switch to the correct TMUX session (or create a new one), create a new TMUX window, name the window properly, and finally move on to the SSH stage.

I was aware of a script that was created by an ex-colleague. The script automated the process by storing the credentials in a key chain; however, it was written in Python and designed to run on a Mac. Not to mention, that long promised Kerberos implementation was just a mirage. I was more likely to see Unicorns running around in the office than a Kerberos solution. What I needed was a native Linux implementation written with BASH!

Before I begin, here are some details about my environment. I run the scripts on my Fedora 20 instance accessing primarily CentOS servers. The SSH hop script is dependent on components such as the KDE Wallet Manager and the expect tool. The TMUX hop script requires the TMUX tool as can be easily guessed from its name.

KDE Wallet Manager Configuration

The first step is to utilize the KDE Wallet Manager to store the SSH username and password in a secure manner. I simply created a new wallet named ssh and protected it with a GPG key. In addition, I created two entries named ssh_username and ssh_password under the Password folder.

SSH Login Automation

This awesome blog post about how to access the KWalletManager via the command line interface was the key for this implementation. The first time the ssh.hop script is called, KDE Wallet Manager responds with a prompt to decrypt the wallet and from then on you can use the script without any prompts (provided that you allow access not just once). Here is the script:

ssh.hop

#!/bin/bash

if [ "$#" -eq 0 ]
then
    echo "Usage: $0 <hostname> [<username>]"
    exit
fi

hostname=$1

namedPipe=/tmp/expect.$$
trap "rm -f $namedPipe; exit;" SIGHUP SIGINT SIGQUIT SIGTERM EXIT

walletClient="ssh.hop"
walletName="ssh"
walletUsernameEntry="ssh_username"
walletPasswordEntry="ssh_password"

walletId=$(qdbus org.kde.kwalletd /modules/kwalletd org.kde.KWallet.open "$walletName" 0 "$walletClient")

if [ -z "$walletId" ]
then
    echo "Unable to get wallet id."
    exit
fi

if [ -z "$2" ]
then

    username=$(qdbus org.kde.kwalletd /modules/kwalletd readPasswordList $walletId Passwords "$walletUsernameEntry" "$walletClient" | awk '{print $2}')
    
    if [ -z "$username" ]
    then
        echo "Unable to get username from wallet."
        exit
    fi

else
    username=$2
fi

password=$(qdbus org.kde.kwalletd /modules/kwalletd readPasswordList $walletId Passwords "$walletPasswordEntry" "$walletClient" | awk '{print $2}')

if [ -z "$password" ]
then
    echo "Unable to get password from wallet."
    exit
fi

export HISTIGNORE="expect*"

mkfifo $namedPipe

echo "
    spawn ssh -o StrictHostKeyChecking=no -t $hostname sudo -i
    expect {
        \"?assword:\" { send \"$password\r\" }
        timeout exit
    }
    expect {
        \"password for $username:\" { send \"$password\r\" }
        timeout exit
    }
    interact" > $namedPipe &
    
expect -f $namedPipe
rm -f $namedPipe

A couple of things to note here... The named pipe is required so that we can background the echo statement and exit. If we don't utilize a named pipe and use a regular echo "expect commands" | expect -f - call, expect fails to work properly since the first process is needed to be kept alive. Because, by default, interact expects the user to be writing stdin and reading stdout of the expect process itself. You can prevent the pipe from closing by running a command like (echo "expect commands"; cat) | expect -f - but that means your process list would be littered with echo statements containing clear text passwords. Long story short, to prevent that from happening, a named pipe was used to keep the process list clean.

TMUX Session and Window Setup Automation

As I mentioned before I also needed a way to manage my TMUX session. So I wrote the tmux.hop script to automate TMUX session and window setup and kick of an SSH login right away utilizing the ssh.hop script included above.

tmux.hop

#!/bin/bash

if [ "$#" -eq 0 ]
then
    echo "Usage: $0 <hostname> [<session>] [<username>]"
    exit
fi

h=$1
s=$2
u=$3

tmux=$TMUX

# if a session name provided
if [ ! -z "$s" ]
then

    tmux has-session -t "$s" &>/dev/null
    if [ "$?" -ne 0 ]
    then
        TMUX=""
        tmux new-session -d -s "$s"
    fi
    
    tmux new-window -t "$s" -n "$h" "ssh.hop $h $u"
    
    if [ -z "$tmux" ]
    then
        tmux attach-session -d -t "$s"
    else
        tmux switch-client -t "$s"
    fi

    tmux select-window -t "$s:$h"
    
# no session name provided
else
    
    if [ -z "$tmux" ]
    then
        echo "No current tmux session."
        exit
    fi
    
    tmuxCurrentSession=$(tmux display-message -p '#S')
    tmux new-window -n "$h" "ssh.hop $h $u"
    tmux select-window -t "$tmuxCurrentSession:$h"

fi

Conclusion

This solution seems to be kinda ugly and not practical; however, I am already saving a lot of cycles in my workday by using these scripts. So in my book, the technology works... :)

Monday, December 23, 2013

Exploiting the /proc filesystem

It has been quite a while since I have written my last article. After pondering what to write for a couple of days, I have decided to share a couple of /proc filesystem tricks. Here is a list of real-life scenarios that illustrate how you can exploit this filesystem:

Environment Variables

Let's start with a simple example. You have a Java process that is misbehaving and you would like to make sure that the JAVA_HOME environment variable for the process context is pointing to the correct JDK installed on the system. Just cat the /proc/[pid]/environ file to view the list of environment variables for the process.

File Descriptors

To get the list of file descriptors opened by a process run an ls command for /proc/[pid]/fd. This directory would list the file descriptors as symbolic links to files or sockets with inode numbers. This is a particularly useful trick in case you are dealing with a long strace output and you have a system call with an associated file descriptor. If the file descriptor is a socket, you can gather even more information by grepping for the inode number in /proc/[pid]/net/tcp, /proc/[pid]/net/udp, or /proc/[pid]/net/unix depending on the socket type.

Swap Space

You are dealing with a server that has started running a process consuming large amounts of memory and the server has swapped some data from memory. It is not big deal for Linux if swapping is not continuous; however, it is a problem from a monitoring perspective because Linux is not going to move that data back into memory. One trick to use the swapoff command to force the data back into memory. If there is not enough space on the other hand, you have to force drop the page caches from memory by running "echo 1 > /proc/sys/vm/drop_caches" and then do a swapoff and a subsequent swapon to free up the swap space.

Connection Tracking

If you ever run into "nf_conntrack: table full, dropping packet" errors... You are using iptables and the kernel is hitting a limit on the number of connections that can be tracked. This is controlled by the net.netfilter.nf_conntrack_max system variable which defaults to 65536. You can either try to increase this default value or cat the /proc/net/nf_conntrack file to figure out what type of connection constitutes the majority of tracked connections and update your iptables rules accordingly to not track them - see the raw table with the NOTRACK option in the iptables manual.

Child Threads

You have a MySQL instance running with a very large connection limit and you have a slave I/O thread that is misbehaving. The MySQL instance is lagging behind yet there seems to be no apparent problem. The next idea you come up with is to strace the I/O thread; however, the "show processlist" output is showing connection ids not Linux process ids. Luckily, there is a solution. If you have persistent database connections, stop the I/O thread and start it again. Then, run an "ls -al" in /proc/[pid]/task which will in turn show you a list of child pids with creation dates. Because you have restarted your I/O thread, the most recently created one should be the I/O thread. You can also verify this by comparing the task creation date to the "Time" column for the I/O thread in the "show processlist" output. If you don't have persistent connections, identifying the correct thread is a bit more tricky. Simply restart the I/O and SQL threads at different times, wait for a while, and then identify the correct thread by comparing the "Time" column from the "show processlist" output again.

Process Limits

Every process has to run within certain limits set by the kernel. One of these limits is the number of child processes that can be spawned by a parent process. For example, when we started running into MySQL connection errors, the underlying problem turned out to be the connection limit (each MySQL connection is served by a child thread) being more than what the kernel allowed for. These limits can be different for each process and can be viewed by catting the /proc/[pid]/limits file. And the best part? You can change the limits dynamically by echoing to this file instead of restarting that production MySQL instance.

Friday, November 23, 2012

Using (R)?ex for Fame and Fortune

Overview

(R)?ex, according to its website, is a tool written in Perl for configuration management and software deployment. The only requirement to have is an SSH connection to the remote server(s).

Alternative Uses

(R)?ex is a very capable tool to automate system administration tasks. For example, I have recently built an RPM build script that copies my local code/files to a build server, builds an RPM package, and downloads the finished RPM to my RPM repository on my local box. I have also used it to generate reports. One recent report was required to see how many of our servers were able to access a certain DNS view.

In fact, the use cases are pretty much unlimited. You can use (R)?ex for any process that requires some sort of automation on a remote server. That said, as powerful as it is, (R)?ex still requires some initial setup to become usable. I have included a couple of my tips below.

Tips and Tricks

Code Structure

Rexify.org Wiki recommends using revision control system externals to distribute your modules among your folders that contain your folders. In my case, I wanted to have a single project checked into my GitHub account so I utilized a symbolic link called "lib" in each of the project folders instead.

/rex
.... /lib
........ /Module
............ Module.pm
.... /project_a
........ lib -> ../lib
........ rexfile_1
........ rexfile_2
.... /project_b
........ lib -> ../lib
........ rexfile_3
........ rexfile_4

Naming (R)?ex Files

The online documentation is not clear about the options you have when it comes to naming your (R)?ex files. The default approach is to use the name "Rexfile". This is not really a good implementation as you don't want to have a bunch of files with the same name sitting around on your hard disk. The solution is to name your files appropriately and use the "-f" for the (R)?ex command to execute your scripts:

rex -f script_name

Code Highlighting

If you are using Eclipse like me, you will have problems when dealing with files without any extensions. The solution is to ensure that you have a Perl file with a ".pl" extension and a "use Rex -base;" statement added to the beginning of your scripts to get rid of all the syntax warnings and errors in your IDE.

Dealing with Hard-Coded Passwords

Remember that the (R)?ex file is actually a Perl script. This means that you can utilize the IO::Prompt package to collect SSH user and password information instead of hard-coding your passwords in each script.

use Rex -base;
use IO::Prompt;

my $username = prompt('Enter username: ');
my $password = prompt('Enter password: ', -e => '*');

user qq/$username/;
password qq/$password/;
pass_auth;

Parallelism

Certain tasks may take longer than expected so do not forget to utilize the "parallelism" call for those scripts.

Logging

(R)?ex output from your script can easily be separated from your script output by piping STDOUT to a file.

rex -f myrex.pl > output.log

However, if you wish your output to look like the (R)?ex output, you can utilize the Rex::Logger module instead.

Thursday, October 11, 2012

Networking with Multiple Guests on VirtualBox

IT Support

I showed up to work on my first day a couple of weeks ago and the first thing that I was given was a company Mac... A Mac!

My absolutely brilliant idea was to ask IT support if I was allowed to wipe the hard-drive clean and install my favorite Linux distribution. After giving me the classic thousand-yard stare that every IT dude gives every time you ask for something, he told me that "it was not supported". Long story short, at the end of the first week, I was left with this piece of brushed Aliminum brick running some applications that does not even run on Linux but required by my new company.

Virtualization

Solution? Of course virtualization. Needless to say, this was a complex setup with multiple requirements: * I want to run a Fedora guest for every day use. There is nothing that can match Konsole running on a bleeding edge distribution out there.
* I also need to run a CentOS guest for certain tasks such as building RPMs and other production related tasks.
* My guest operating systems must be able to utilize the VPN connection provided by the host computer.
* My guest OSes have to communicate with each other.
* Services running on my guest OSes need to be accessible from the outside if necessary.
* I keep the Mac just for establishing a VPN connection and for other company related resources like mail.

I am not going to get into the details of setting up a guest OS in this article. I will primarily focus on the networking setup. You should stop reading at this point and have a look at this superb blog article that documents how each networking option work in VirtualBox.

OK, now that you are back and have an idea about how different network setups work in VirtualBox, we can go over the configuration details to satisfy our requirements.

VPN Access

I have a software token installed on the Mac which provides a two factor authentication mechanism to establish a VPN connection. The problem is this is a TUN device. This means that it operates on Layer 3 (Network) packets. This is an important point because we have to select a NAT network adapter in the network settings for our Guest OSes. The other option - a bridged network adapter - breaks the VPN setup; this is is due to packets being processed on Layer 2 (Datalink) bypassing the VPN setup on Layer 3 (Network).

Inter-Guest OS Communication

The NAT setup to maintain VPN access described above comes with a price. VirtualBox assigns the same IP to all NAT adapter guests effectively preventing any inter-guest communication. We have to have a different IP for each of our guests to be able to communicate. Luckily, VirtualBox provides multiple adapters for this purpose.

First, define a host-only network under global Virtual box network settings. Second, enable the DHCP server for the host-only network so your host provides an IP address to each guest automatically. Finally, under guest network settings, select the host-only network adapter under the Adapter 2 tab. Run the ifconfig command on your guest and you shall see two network adapters: one with a NAT setup for VPN access and the other with a unique IP address for communication.

Outside Access for Guest Services

Port forwarding to the rescue. This is the same concept as configuring port-forwarding on a router. The port forwarding configuration section is available under the advanced NAT adapter settings for your guest operating system. See the link above for more information.

Friday, September 14, 2012

Android OAuth2 REST client with Spring for Android

Overview

In this article, I will try to explain how to design an Android OAuth2 client that can interact with a Smyfony2 back-end implementation using Spring for Android and Spring Social.

Before we start, I need to emphasize a couple of points. First of all, I am not an Android expert; I am still learning. Secondly, I like to leverage existing tools so I have chosen Spring Social for Android for this article. It satisfies my requirements for this tutorial but it may not be suitable for every case. Finally, I will not be explaining the server side implementation in this article. Read my previous FOSRestBundle and FOSOAuthServerBundle articles if you need starters.

Raw Data

Let's first look at the raw data that we will fetch from the server. Because I have been using the Conversation and Message examples for a while now, I will stick with the same pattern in this article. Let's say we would like to retrieve a list of conversations by a specific user from a REST endpoint located at /api/users/4f7f79ac7f8b9a000f000001/conversations.json. As you can see in the JSON output below, the data we will receive is a simple list that contains a single conversation with two messages:

[
    {
        "id": "501ee4b27f8b9aa905000000",
        "created_at": "2012-08-05T14:25:06-0700",
        "modified_at": "2012-08-25T00:08:27-0700",
        "replied_id": "503879eb7f8b9aa505000000",
        "replied_at": "2012-08-25T00:08:27-0700",
        "replied_by": "4f7f79ac7f8b9a000f000001",
        "replied_body": "test",
        "messages": [
            {
                "id": "501ee4b27f8b9aa905000001",
                "user_id": "500b3d027f8b9aeb2f000002",
                "body": "Hello Burak!",
                "created_at": "2012-08-05T14:25:06-0700",
                "modified_at": "2012-08-05T14:25:06-0700"
            },
            {
                "id": "501ef8bb7f8b9aa905000005",
                "user_id": "4f7f79ac7f8b9a000f000001",
                "body": "How are you?",
                "created_at": "2012-08-05T15:50:35-0700",
                "modified_at": "2012-08-05T15:50:35-0700"
            }
        ],
        "from_user": {
            "username": "burcu",
            "name_first": "Burcu",
            "name_last": "Seydioglu",
            "created_at": "2012-07-21T16:36:34-0700",
            "modified_at": "2012-08-05T13:22:06-0700",
            "avatar": "http://s3.amazonaws.com/dev.media.acme.com/avatar/50/00/03/02/07/08/09/00/02/00/00/02/500b3d027f8b9aeb2f000002",
            "id": "500b3d027f8b9aeb2f000002"
        },
        "to_user": {
            "username": "buraks78",
            "name_first": "Burak",
            "name_last": "Seydioglu",
            "created_at": "2012-04-06T16:18:04-0700",
            "modified_at": "2012-09-11T21:54:39-0700",
            "avatar": "http://s3.amazonaws.com/dev.media.acme.com/avatar/04/07/79/00/07/08/09/00/00/00/00/01/4f7f79ac7f8b9a000f000001",
            "id": "4f7f79ac7f8b9a000f000001"
        }
    }
]

Code Structure

We will follow a similar package pattern to the spring-social-facebook package and create three packages:

com.acme.social.api
This package will contain our entity classes. In this particular case, we are talking about three entities: Conversation, Message, and User. This same package will also include our template classes containing our API methods.

com.acme.social.api.json
This packge will contain all the necessary classes that handle serialization/de-serialization of our Java entity classes from/to JSON.

com.acme.social.connect
Finally, this package will contain our connection classes that will bind everything together.

Entity Classes

These classes are pretty straight forward so I will just share the code and keep the discussion to a minimum.

User.java

package com.acme.social.api;

import java.util.Date;

import android.os.Parcel;
import android.os.Parcelable;

public class User implements Parcelable {
    
    protected String id;
    protected String username;
    protected String nameFirst;
    protected String nameLast;
    protected String avatar;
    protected Date createdAt;
    protected Date modifiedAt;
    
    public User(String id, String username, String nameFirst, String nameLast, String avatar, Date createdAt, Date modifiedAt) {
        this.id = id;
        this.username = username;
        this.nameFirst = nameFirst;
        this.nameLast = nameLast;
        this.avatar = avatar;
        this.createdAt = createdAt;
        this.modifiedAt = modifiedAt;
    }
    
    public User(String id, String username, String nameFirst, String nameLast, String avatar) {
        this.id = id;
        this.username = username;
        this.nameFirst = nameFirst;
        this.nameLast = nameLast;
        this.avatar = avatar;
    }
    
    public User(Parcel in) {
        readFromParcel(in);
    }

    public String getAvatar() {
        return avatar;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public String getId() {
        return id;
    }

    public Date getModifiedAt() {
        return modifiedAt;
    }
    
    public String getName() {
     return nameFirst + " " + nameLast;
    }

    public String getNameFirst() {
        return nameFirst;
    }

    public String getNameLast() {
        return nameLast;
    }

    public String getUsername() {
        return username;
    } 
    
    @Override
 public int describeContents() {
  return 0;
 }

 @Override
 public void writeToParcel(Parcel out, int flags) {
  out.writeString(id);
  out.writeString(username);
  out.writeString(nameFirst);
  out.writeString(nameLast);
  out.writeString(avatar);
  out.writeSerializable(createdAt);
  out.writeSerializable(modifiedAt);
 }
 
 private void readFromParcel(Parcel in) {
  id = in.readString();
  username = in.readString();
  nameFirst = in.readString();
  nameLast = in.readString();
  avatar = in.readString();
  createdAt = (Date) in.readSerializable();
  modifiedAt = (Date) in.readSerializable();
 }
 
 public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
  
        public User createFromParcel(Parcel in) {
            return new User(in);
        }
 
        public User[] newArray(int size) {
            return new User[size];
        }
        
    };

}

Message.java

package com.acme.social.api;

import java.util.Date;

import android.os.Parcel;
import android.os.Parcelable;

public class Message implements Parcelable {
    
 private String id;
    private String userId;
    private String body;
    private Date createdAt;
    private Date modifiedAt;
    // database related
    protected String userUsername;
 protected String userNameFirst;
 protected String userNameLast;
 protected String userAvatar;
    
    public Message(String id, String userId, String body, Date createdAt, Date modifiedAt) {
     this.id = id;
        this.userId = userId;
        this.body = body;
        this.createdAt = createdAt;
        this.modifiedAt = modifiedAt;
    }
    
    public Message(String id, String userId, String body, Date createdAt, Date modifiedAt, String userUsername, String userNameFirst, String userNameLast, String userAvatar) {
     this.id = id;
     this.userId = userId;
        this.body = body;
        this.createdAt = createdAt;
        this.modifiedAt = modifiedAt;
  this.userUsername = userUsername;
  this.userNameFirst = userNameFirst;
  this.userNameLast = userNameLast;
  this.userAvatar = userAvatar;
 }
    
    public Message(Parcel in) {
     readFromParcel(in);
    }
    
    public String getId() {
        return id;
    }
   
    public String getBody() {
        return body;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public Date getModifiedAt() {
        return modifiedAt;
    }

    public String getUserId() {
        return userId;
    }
    
    public String getUserUsername() {
  return userUsername;
 }
    
    public String getUserNameFirst() {
  return userNameFirst;
 }
    
    public String getUserNameLast() {
  return userNameLast;
 }
 
 public String getUserName() {
  return userNameFirst + " " + userNameLast;
 }

 public String getUserAvatar() {
  return userAvatar;
 }
 
 public void setUserId(String userId) {
  this.userId = userId;
 }

    @Override
 public int describeContents() {
  return 0;
 }

 @Override
 public void writeToParcel(Parcel out, int flags) {
  out.writeString(id);
  out.writeString(userId);
  out.writeString(userUsername);
  out.writeString(userNameFirst);
  out.writeString(userNameLast);
  out.writeString(userAvatar);
  out.writeString(body);
  out.writeSerializable(createdAt);
  out.writeSerializable(modifiedAt);
 }
 
 private void readFromParcel(Parcel in) {
  id = in.readString();
  userId = in.readString();
  userUsername = in.readString();
  userNameFirst = in.readString();
  userNameLast = in.readString();
  userAvatar = in.readString();
  body = in.readString();
  createdAt = (Date) in.readSerializable();
  modifiedAt = (Date) in.readSerializable();
 }
 
 public static final Parcelable.Creator<Message> CREATOR = new Parcelable.Creator<Message>() {
  
        public Message createFromParcel(Parcel in) {
            return new Message(in);
        }
 
        public Message[] newArray(int size) {
            return new Message[size];
        }
        
    };
    
}

Conversation.java

package com.acme.social.api;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import android.os.Parcel;
import android.os.Parcelable;

public class Conversation implements Parcelable {
    
    private String id;
    private User fromUser;
    private User toUser;
    private Date createdAt;
    private Date modifiedAt;
    private String repliedId;
    private Date repliedAt;
    private String repliedBy;
    private String repliedBody;
    private String avatar;
    private List<Message> messages;
    
    public Conversation(String id, User fromUser, User toUser, Date createdAt, Date modifiedAt, String repliedId, Date repliedAt, String repliedBy, String repliedBody) {
        this.id = id;
        this.fromUser = fromUser;
        this.toUser = toUser;
        this.createdAt = createdAt;
        this.modifiedAt = modifiedAt;
        this.repliedId = repliedId;
        this.repliedAt = repliedAt;
        this.repliedBy = repliedBy;
        this.repliedBody = repliedBody;
    }
    
    public Conversation(Parcel in) {
     readFromParcel(in);
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public User getFromUser() {
        return fromUser;
    }

    public String getId() {
        return id;
    }

    public List<Message> getMessages() {
        return messages;
    }
    
    public Message getLastMessage() {
     if (messages != null && !messages.isEmpty()) {
      return messages.get(messages.size() - 1);
  }
     return null;
    }

    public Date getModifiedAt() {
        return modifiedAt;
    }
    
    public String getRepliedId() {
        return repliedId;
    }

    public Date getRepliedAt() {
        return repliedAt;
    }

    public String getRepliedBy() {
        return repliedBy;
    }
    
    public String getRepliedBody() {
     return repliedBody;
    }

    public User getToUser() {
        return toUser;
    }
    
    
 public String getAvatar() {
  return avatar;
 }

 public void getAvatar(String avatar) {
  this.avatar = avatar;
 }

 @Override
 public int describeContents() {
  return 0;
 }

 @Override
 public void writeToParcel(Parcel out, int flags) {
  out.writeString(id);
  out.writeParcelable(fromUser, flags);
  out.writeParcelable(toUser, flags);
  out.writeSerializable(createdAt);
  out.writeSerializable(modifiedAt);
  out.writeSerializable(repliedAt);
  out.writeList(messages);
 }
 
 private void readFromParcel(Parcel in) {
     id = in.readString();
     fromUser = in.readParcelable(User.class.getClassLoader());
     toUser = in.readParcelable(User.class.getClassLoader());
     createdAt = (Date) in.readSerializable();
     modifiedAt = (Date) in.readSerializable();
     repliedAt = (Date) in.readSerializable();
     messages = new ArrayList<Message>();
     in.readList(messages, Message.class.getClassLoader());
 }
 
 public static final Parcelable.Creator<Conversation> CREATOR = new Parcelable.Creator<Conversation>() {
  
        public Conversation createFromParcel(Parcel in) {
            return new Conversation(in);
        }
 
        public Conversation[] newArray(int size) {
            return new Conversation[size];
        }
        
    };
    
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Conversation)) {
           return false;
        }
        return this.getId().equals(((Conversation) obj).getId());
    }

}

You may have noticed the Parcelable interface implemented by all these entities. This is required because all entity class instances will be generated and returned by an IntentService as I will explain in my next article. If you don't know what the Parcelable interface is used for, have a look at my previous A Parcelable Tutorial for Android article.

In addition to the above entity classes, we also need to create a Profile class which represents the current authenticated user:

Profile.java

package com.acme.social.api;

import java.util.Date;
import java.util.List;

import android.os.Parcel;
import android.os.Parcelable;

public class Profile implements Parcelable {
    
    protected String id;
    protected String nameFirst;
    protected String nameLast;
    protected String username;
    protected String email;
    protected String avatar;
    protected Date createdAt;
    protected Date modifiedAt;
    
    public Profile(String id, String nameFirst, String nameLast, String username, String email, String avatar, Date createdAt, Date modifiedAt) {
        this.id = id;
        this.nameFirst = nameFirst;
        this.nameLast = nameLast;
        this.username = username;
        this.email = email;
        this.avatar = avatar;
        this.createdAt = createdAt;
        this.modifiedAt = modifiedAt;
    }

    public Profile(Parcel in) {
        readFromParcel(in);
    }
    
    public String getAvatar() {
        return avatar;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public String getId() {
        return id;
    }

    public Date getModifiedAt() {
        return modifiedAt;
    }

    public String getNameFirst() {
        return nameFirst;
    }

    public String getNameLast() {
        return nameLast;
    }

    public String getUsername() {
        return username;
    } 
    
    public String getEmail() {
        return email;
    } 

 @Override
 public int describeContents() {
  return 0;
 }

 @Override
 public void writeToParcel(Parcel out, int flags) {
  out.writeString(id);
  out.writeString(nameFirst);
  out.writeString(nameLast);
  out.writeString(username);
  out.writeString(email);
  out.writeString(avatar);
  out.writeSerializable(createdAt);
  out.writeSerializable(modifiedAt);
 }
 
 private void readFromParcel(Parcel in) {
  id = in.readString();
  nameFirst = in.readString();
  nameLast = in.readString();
  username = in.readString();
  email = in.readString();
  avatar = in.readString();
  createdAt = (Date) in.readSerializable();
  modifiedAt = (Date) in.readSerializable();
 }
 
 public static final Parcelable.Creator<Profile> CREATOR = new Parcelable.Creator<Profile>() {
  
        public Profile createFromParcel(Parcel in) {
            return new Profile(in);
        }
 
        public Profile[] newArray(int size) {
            return new Profile[size];
        }
        
    };
    
    public User getUser() {
     return new User(id, username, nameFirst, nameLast, avatar, createdAt, modifiedAt);
    }

}

JSON Mapping

Now that our entity classes are defined, we can move onto creating our mapping classes inside the com.acme.social.api.json package. It is pretty straight-forward Jackson configuration as illustrated below:

UserMixin.java

package com.acme.social.api.json;

import java.util.Date;

import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.annotate.JsonSerialize;

@JsonIgnoreProperties(ignoreUnknown = true)
public class UserMixin {
    
    @JsonCreator
 UserMixin(
  @JsonProperty("id") String id, 
  @JsonProperty("username") String username, 
        @JsonProperty("name_first") String nameFirst, 
        @JsonProperty("name_last") String nameLast, 
        @JsonProperty("avatar") String avatar,
        @JsonProperty("created_at") Date createdAt,
        @JsonProperty("modified_at") Date modifiedAt
    ) {}
    
    @JsonProperty("created_at")
 @JsonSerialize(using = DateSerializer.class)
 Date createdAt;
       
    @JsonProperty("modified_at")
 @JsonSerialize(using = DateSerializer.class)
 Date modifiedAt;
}

MessageMixin.java

package com.acme.social.api.json;

import java.util.Date;

import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.annotate.JsonSerialize;

@JsonIgnoreProperties(ignoreUnknown = true)
public class MessageMixin {
    
    @JsonCreator
 MessageMixin(
  @JsonProperty("id") String id, 
        @JsonProperty("user_id") String userId, 
        @JsonProperty("body") String body,
        @JsonProperty("created_at") Date createdAt,
        @JsonProperty("modified_at") Date modifiedAt
    ) {}
    
    @JsonProperty("created_at")
 @JsonSerialize(using = DateSerializer.class)
 Date createdAt;
       
    @JsonProperty("modified_at")
 @JsonSerialize(using = DateSerializer.class)
 Date modifiedAt;
   
}

ConversationMixin.java

package com.acme.social.api.json;

import java.io.IOException;
import java.util.Date;
import java.util.List;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonDeserialize;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.type.TypeReference;

import com.acme.social.api.Message;
import com.acme.social.api.User;

@JsonIgnoreProperties(ignoreUnknown = true)
public class ConversationMixin {
    
    @JsonCreator
 ConversationMixin(
        @JsonProperty("id") String id, 
        @JsonProperty("from_user") User fromUser,
        @JsonProperty("to_user") User toUser,
        @JsonProperty("created_at") Date createdAt,
        @JsonProperty("modified_at") Date modifiedAt,
        @JsonProperty("replied_id") String repliedId,
        @JsonProperty("replied_at") Date repliedAt,
        @JsonProperty("replied_by") String repliedBy,
        @JsonProperty("replied_body") String repliedBody
    ) {}
    
    @JsonProperty("created_at")
 @JsonSerialize(using = DateSerializer.class)
 Date createdAt;
       
    @JsonProperty("modified_at")
 @JsonSerialize(using = DateSerializer.class)
 Date modifiedAt;
    
    @JsonProperty("replied_at")
 @JsonSerialize(using = DateSerializer.class)
 Date repliedAt;
   
    @JsonProperty("messages")
    @JsonDeserialize(using = MessageListDeserializer.class)
    List<Message> messages;
    
    private static class MessageListDeserializer extends JsonDeserializer<List<Message>> {
        
        @SuppressWarnings("unchecked")
        @Override
        public List<Message> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            
            ObjectMapper mapper = new ObjectMapper();
            mapper.setDeserializationConfig(ctxt.getConfig());
            jp.setCodec(mapper);
            
            if(jp.hasCurrentToken()) {
                JsonNode dataNode = jp.readValueAsTree();
                if(dataNode != null) {
                    return (List<Message>) mapper.readValue(dataNode, new TypeReference<List<Message>>() {});
                }
            }

            return null;
        }
        
    }
}

ProfileMixin.java

package com.acme.social.api.json;

import java.io.IOException;
import java.util.Date;
import java.util.List;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonDeserialize;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.type.TypeReference;

@JsonIgnoreProperties(ignoreUnknown = true)
public class ProfileMixin {
    
    @JsonCreator
    ProfileMixin(
  @JsonProperty("id") String id, 
        @JsonProperty("name_first") String nameFirst, 
        @JsonProperty("name_last") String nameLast, 
        @JsonProperty("username") String username, 
        @JsonProperty("email") String email, 
        @JsonProperty("avatar") String avatar,
        @JsonProperty("created_at") Date createdAt,
        @JsonProperty("modified_at") Date modifiedAt
    ) {}
    
    @JsonProperty("created_at")
 @JsonSerialize(using = DateSerializer.class)
 Date createdAt;
       
    @JsonProperty("modified_at")
 @JsonSerialize(using = DateSerializer.class)
 Date modifiedAt;
}

You may have noticed the DateSerializer.class references. This custom serializer class is necessary because we need to generate an ISO8601 formatted dates when serializing our entities. This is required not only for consistency purposes - the dates we receive are in this format - but also the format needs to be recognizable by PHP's DateTime constructor when serialized JSON is unmarshalled on the server.

DateSerializer.java

package com.acme.social.api.json;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;

public class DateSerializer extends JsonSerializer<Date> {
 @Override
 public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
  SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); // iso8601
  String formattedDate = formatter.format(value);
  jgen.writeString(formattedDate);
 }
}

Finally, let's couple our entity classes with their corresponding mixin classes. We will utilize a module class to accomplish this as illustrated below:

AcmeModule.java

package com.acme.social.api.json;

import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.module.SimpleModule;

import com.acme.social.api.Conversation;
import com.acme.social.api.Message;
import com.acme.social.api.Profile;
import com.acme.social.api.User;

public class AcmeModule extends SimpleModule {
    
    public AcmeModule() {
  super("AcmeModule", new Version(1, 0, 0, null));
 }
    
    @Override
 public void setupModule(SetupContext context) {    
  context.setMixInAnnotations(User.class, UserMixin.class);
  context.setMixInAnnotations(Profile.class, ProfileMixin.class);
  context.setMixInAnnotations(Conversation.class, ConversationMixin.class);
  context.setMixInAnnotations(Message.class, MessageMixin.class);
    }
}

Template Classes

Template classes contain all our API methods to interact with the remote server. We will create a template class for each of our entities (unless they are embedded).

UserTemplate.java

package com.acme.social.api;

public class UserTemplate extends BaseTemplate {
    
 private final AcmeTemplate api;
    
    public UserTemplate(AcmeTemplate api, boolean isAuthorized) {
        super(isAuthorized);
        this.api = api;
    }

}

ConversationTemplate.java

package com.acme.social.api;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;

public class ConversationTemplate extends BaseTemplate {
    
 private final AcmeTemplate api;
    
    public ConversationTemplate(AcmeTemplate api, boolean isAuthorized) {
        super(isAuthorized);
        this.api = api;
    }
    
    public List<Conversation> getConversations(String userId) {
     return api.fetchCollection("/users/" + userId + "/conversations.json?limit=" + ConversationTemplate.DEFAULT_LIMIT, Conversation.class, null);
 }
    
    public Conversation getConversation(String userId, String conversationId) {
     return api.fetchObject("/users/" + userId + "/conversations/" + conversationId + ".json", Conversation.class, null);
    }
    
    public List<Message> getMessages(String userId, String conversationId) {
     return api.fetchCollection("/users/" + userId + "/conversations/" + conversationId + "/messages.json", Message.class, null);
    }
    
    public Message postMessage(String userId, String conversationId, Message message) {
     return api.postForObject("/users/" + userId + "/conversations/" + conversationId + "/messages.json", message, Message.class);
    }
    
}

ProfileTemplate.java

package com.acme.social.api;

public class ProfileTemplate extends BaseTemplate {
    
 private final AcmeTemplate api;
    
    public ProfileTemplate(AcmeTemplate api, boolean isAuthorized) {
        super(isAuthorized);
        this.api = api;
    }
    
    public Profile getProfile() {
     return api.fetchObject("/profiles/me.json", Profile.class, null);
    }

    
}

Finally, we create a base template class called AcmeTemplate to tie everything together. This class contains a couple of wrapper methods (i.e. postForObject, fetchObject, fetchCollection) - not a complete list but enough to serve the purpose of this article - to handle URL generation. It also contains a method called registerAcmeJsonModule which binds our JSON setup (AcmeModule) to the REST template provided by Spring for Android and ensures that the property naming strategy is correct (lowercase words separated with underscores).

AcmeTemplate.java

package com.acme.social.api;

import java.io.IOException;
import java.util.List;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.PropertyNamingStrategy;
import org.codehaus.jackson.map.type.CollectionType;
import org.codehaus.jackson.map.type.TypeFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.social.UncategorizedApiException;
import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
import org.springframework.social.support.ClientHttpRequestFactorySelector;
import org.springframework.social.support.URIBuilder;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import com.acme.social.api.json.AcmeModule;

public class AcmeTemplate extends AbstractOAuth2ApiBinding implements Acme {
    
    private static final String ACME_API_URL = "http://10.0.2.2/app_dev.php/api";
 private UserTemplate userOperations;
 private ProfileTemplate profileOperations;
    private ConversationTemplate conversationOperations;
    
    private ObjectMapper objectMapper;
    
    public AcmeTemplate() {
        initialize();  
    }
    
    public AcmeTemplate(String accessToken) {
  super(accessToken);
  initialize();
 }
    
    public void initSubApis() {
        this.userOperations = new UserTemplate(this, isAuthorized());
        this.profileOperations = new ProfileTemplate(this, isAuthorized());
        this.conversationOperations = new ConversationTemplate(this, isAuthorized());
    }
    
    public UserTemplate userOperations() {
        return this.userOperations;
    }
    
    public ProfileTemplate profileOperations() {
        return this.profileOperations;
    }
    
    public ConversationTemplate conversationOperations() {
        return this.conversationOperations;
    }
    
    private void initialize() {
  registerAcmeJsonModule(getRestTemplate());
  getRestTemplate().setErrorHandler(new DefaultResponseErrorHandler());
  // Wrap the request factory with a BufferingClientHttpRequestFactory so that the error handler can do repeat reads on the response.getBody()
  super.setRequestFactory(ClientHttpRequestFactorySelector.bufferRequests(getRestTemplate().getRequestFactory()));
  initSubApis();
 }
  
 private void registerAcmeJsonModule(RestTemplate restTemplate2) {
        
  this.objectMapper = new ObjectMapper();    
  this.objectMapper.registerModule(new AcmeModule());
  this.objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy());
        
  List<HttpMessageConverter<?>> converters = getRestTemplate().getMessageConverters();
  for (HttpMessageConverter<?> converter : converters) {
   if(converter instanceof MappingJacksonHttpMessageConverter) {
    MappingJacksonHttpMessageConverter jsonConverter = (MappingJacksonHttpMessageConverter) converter;
    jsonConverter.setObjectMapper(objectMapper);
   }
  }
 }
 
 public <T> T postForObject(String path, Object request, Class<T> responseType) {
  URIBuilder uriBuilder = URIBuilder.fromUri(ACME_API_URL + path);
  return getRestTemplate().postForObject(uriBuilder.build(), request, responseType);
 }
 
 public <T> T fetchObject(String path, Class<T> type, MultiValueMap<String, String> queryParameters) {
  URIBuilder uriBuilder = URIBuilder.fromUri(ACME_API_URL + path);
  if (queryParameters != null) {
   uriBuilder.queryParams(queryParameters);
  }
  return getRestTemplate().getForObject(uriBuilder.build(), type);
 }
 
 public <T> List<T> fetchCollection(String path, Class<T> type, MultiValueMap<String, String> queryParameters) {
  URIBuilder uriBuilder = URIBuilder.fromUri(ACME_API_URL + path);
  if (queryParameters != null) {
   uriBuilder.queryParams(queryParameters);
  }
  JsonNode dataNode = getRestTemplate().getForObject(uriBuilder.build(), JsonNode.class);
  return deserializeDataList(dataNode, type);
 }
 
 @SuppressWarnings("unchecked")
 private <T> List<T> deserializeDataList(JsonNode jsonNode, final Class<T> elementType) {
  try {
   CollectionType listType = TypeFactory.defaultInstance().constructCollectionType(List.class, elementType);
   return (List<T>) objectMapper.readValue(jsonNode, listType);
  } catch (IOException e) {
   throw new UncategorizedApiException("Error deserializing data from: " + e.getMessage(), e);
  }
 }

}

Connection Classes

Before we go into code deatils, have a look at the below UML class diagram that explains our setup. We are basically going to model our design after existing packages already provided by Spring Social; see the Facebook package by Spring Social for reference.

Spring Social - UML Diagram

In the above UML class diagram, light green boxes represent our custom classes while the yellow ones represent classes provided by Spring Social.

AcmeConnectionFactory
This class extends the OAuth2ConnectionFactory class. As you can already infer from the naming convention, this class is responsible for generating an OAuth2Connection. Its constructor injects our own custom AcmeServiceProvider and AcmeAdapter class instances into the base connection factory class.

AcmeConnectionFactory.java

package com.acme.social.connect;

import com.acme.social.api.Acme;
import org.springframework.social.connect.support.OAuth2ConnectionFactory;

public class AcmeConnectionFactory extends OAuth2ConnectionFactory<Acme> {

 public AcmeConnectionFactory(String clientId, String clientSecret, String authorizeUrl, String tokenUrl) {
  super("Acme", new AcmeServiceProvider(clientId, clientSecret, authorizeUrl, tokenUrl), new AcmeAdapter());
 }

}

AcmeAdapter
As can be seen in the UML diagram above, AcmeAdapter is a component of OAuth2ConnectionFactory and OAuth2Connection classes. This allows us to inject our authenticated user information (i.e. id, username, and avatar) to the active connection so that we can extract authenticated user details from the connection later when needed in the application. In fact, if you examine the SQLite database table containing the connection information after a successful authentication, you can see that there is a column for provider user id and this class serves as the key mechanism to populate that column.

AcmeAdapter.java

package com.acme.social.connect;

import com.acme.social.api.Acme;
import com.acme.social.api.Profile;

import org.springframework.social.ApiException;
import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.ConnectionValues;
import org.springframework.social.connect.UserProfileBuilder;

public class AcmeAdapter implements ApiAdapter<Acme> {
    
    @Override
    public boolean test(Acme acme) {
  try {
   return true;
  } catch (ApiException e) {
            e = null;
   return false;
  }
 }

    @Override
 public void setConnectionValues(Acme acme, ConnectionValues values) {
  Profile profile = acme.profileOperations().getProfile();
  values.setProviderUserId(profile.getId());
  values.setDisplayName(profile.getUsername());
  values.setImageUrl(profile.getAvatar());
 }

    @Override
 public org.springframework.social.connect.UserProfile fetchUserProfile(Acme acme) {
     
  Profile profile = acme.profileOperations().getProfile();
        
  return new UserProfileBuilder().setName(profile.getUsername())
            .setFirstName(profile.getNameFirst())
            .setLastName(profile.getNameLast())
            .setEmail(profile.getEmail())
            .setUsername(profile.getUsername())
            .build();
        
 }
 
    @Override
 public void updateStatus(Acme acme, String message) {
  acme.profileOperations().updateStatus(message);
 }

}

AcmeServiceProvider
This class extends AbstractOAuth2ServiceProvider and is responsible for binding our AcmeTemplate class with the OAuth2Template class that makes OAuth2 calls using the provided REST template.

AcmeServiceProvider.java

package com.acme.social.connect;

import com.acme.social.api.acme;
import com.acme.social.api.AcmeTemplate;
import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
import org.springframework.social.oauth2.OAuth2Template;

public class AcmeServiceProvider extends AbstractOAuth2ServiceProvider<Acme> {

 public AcmeServiceProvider(String clientId, String clientSecret, String authorizeUrl, String tokenUrl) {
  super(new OAuth2Template(clientId, clientSecret, authorizeUrl, tokenUrl));
 }
    
    @Override
 public Acme getApi(String accessToken) {
  return new AcmeTemplate(accessToken);
 }
 
}

And with this last class defined, our setup is complete. In my next article, I will explain how to use these classes inside an Android application.