Emoji on Ubuntu Trusty

OK, so this is maybe kinda useless. But I wanted an emoji ONCE for a presentation, and the ONCE I wanted it, the fool thing wouldn’t display on my presentation laptop and I had to scramble at the last minute to do something that wasn’t quite as entertaining. So here’s how you fix that problem:

you@box:~$ sudo apt-get update ; sudo apt-get install ttf-ancient-fonts unifont

Poof, you got emojis. In my case, the one I wanted was this:

?

Yes, I AM comfortable in my masculinity, why do you ask…?

Blindrename.pl – a tool to aid blinded analysis in a lab setting

I made a tiny contribution to science this morning – a friend in neuroscience lamented that she couldn’t find any tools to automate the process of renaming a set of images for blinded analysis, so I made one.

https://github.com/jimsalterjrs/blindanalysis

TL;DR on what it does: you feed it a folder full of files, and it renames them all to random names while preserving their original extensions (such as .tif, .lsm, .jpeg, etc). While doing so, it creates a keyfile.csv which ties the original filename to the new, randomized filename – so that you can open up keyfile.csv in Excel, LibreOffice Calc, etc after your blind analysis is done and associate your blind results with your original data.

It’s reasonably smart and cautious – it refuses to run as root, won’t mess with dotfiles or subdirectories, won’t traverse subdirectories, won’t let you accidentally randomize the same folder twice, and spits out human-readable errors if things go wrong.

This is what it looks like in operation:

me@banshee:~$ ls -l /tmp/test
total 24
-rw-rw-r-- 1 me me 2 Oct 14 13:44 1.tif
-rw-rw-r-- 1 me me 2 Oct 14 13:44 2.tif
-rw-rw-r-- 1 me me 2 Oct 14 13:44 3.tif
-rw-rw-r-- 1 me me 2 Oct 14 13:44 4.tif
-rw-rw-r-- 1 me me 2 Oct 14 13:44 5
drwxrwxr-x 2 me me 4096 Oct 14 12:56 subdir

me@banshee:~$ blindrename.pl /tmp/test
Renaming: 1.tif... 2.tif... 3.tif... 4.tif... 5...
5 files successfully blind renamed; keyfile saved to /tmp/test/keyfile.csv.

me@banshee:~$ ls -l /tmp/test
total 28
-rw-rw-r-- 1 me me 2 Oct 14 13:44 B4LOz.tif
-rw-rw-r-- 1 me me 2 Oct 14 13:44 Ek76e.tif
-rw-rw-r-- 1 me me 2 Oct 14 13:44 kdVFM.tif
-rw-rw-r-- 1 me me 131 Oct 14 14:02 keyfile.csv
-rw-rw-r-- 1 me me 2 Oct 14 13:44 Oklr1
drwxrwxr-x 2 me me 4096 Oct 14 12:56 subdir
-rw-rw-r-- 1 me me 2 Oct 14 13:44 wsy7e.tif

me@banshee:~$ cat /tmp/test/keyfile.csv
"Original Filename","Cloaked Filename"
"1.tif","kdVFM.tif"
"2.tif","Ek76e.tif"
"3.tif","B4LOz.tif"
"4.tif","wsy7e.tif"
"5","Oklr1"

There are no dependencies other than Perl itself, and the script is licensed GPLv3 – free for all to use, as in beer and as in speech. I hope this helps somebody (else); this task has got to come up frequently enough in all sorts of labwork that a free tool should be easy to find!

Future science workers: if this helped you and you’re feeling grateful, the EFF can always use a donation, whether large, small, or micro. =)

Another KVM storage comparison article

http://www.ilsistemista.net/index.php/virtualization/47-zfs-btrfs-xfs-ext4-and-lvm-with-kvm-a-storage-performance-comparison.html

Good stuff. Nice in-depth run of several different benchmarks for everything from fileserver to mailserver to database type usage. A bit thin on the ground for configuration, and probably no surprises here if you already read my KVM storage article from 2013, but it’s always nice to get completely independent confirmation.

The author’s hardware setup was surprisingly wimpy – an AMD Phenom II with only 8GB of RAM – which may explain part of why the advanced filesystems did even more relatively poorly than expected in his testing. (ZFS did fine, but it didn’t blow the doors off of everything else the way it did in my testing, which was on a machine with four times as much RAM onboard. And btrfs absolutely tanked, across the board, whereas in my experience it’s typically more a case of “btrfs works really well until it works really badly.”) It was also interesting and gratifying to me that this article tested on CentOS, where mine tested on Ubuntu. Not that I expected any tremendous changes, but it’s always nice to see things holding up across distributions!

Thank you for the article, Gionatan – your work is appreciated!

Nagios initial configuration / NSClient++ / check_nt

A note to my future self:

Not ALL of the configs for a newly installed Nagios server are in /etc/nagios3/conf.d. Some of them are in /etc/nagios-plugins/config. In particular, the configuration for the raw check_nt command is in there, and it’s a little buggy. You’ll need to specify the correct port, and you’ll need to make it pass more than one argument along.

This is the commented-out dist definition of check_nt, followed by the correct way to define check_nt:

# 'check_nt' command definition
#define command {
# command_name check_nt
# command_line /usr/lib/nagios/plugins/check_nt -H '$HOSTADDRESS$' -v '$ARG1$'
#}

define command {
command_name check_nt
command_line /usr/lib/nagios/plugins/check_nt -H $HOSTADDRESS$ -p 12489 -v '$ARG1$' '$ARG2$'
}

You’re welcome, future self. You’re welcome.

Blank reports in SimpleInvoices

its-all-burningFor any fellow SimpleInvoices users – here is a godawful monkeypatch which works around the “blank reports” problem in SI.

A little background: the issue is that phpreports, which is long obsolete code SI uses to generate reports, does call by references in a couple of places. Worse, it does it using the eval() function on a variable populated from an object. I threw up in my mouth a little bit just typing that.

I frankly couldn’t be stuffed to chase everything ALL the way down to the end to find the object method which populates the string and fix IT, but I DID write a simple six line “monkey patch” which at least fixes the output so that reports would work.

In library/phpmaker/PHPReportMaker.php, find the following line:

$sRst = $this->_oProc->run();

And IMMEDIATELY after that line, insert these lines:

// this is a godawful monkeypatch to keep the eval($sRst) line from
// trying to do pass-by-reference function calls. This is awful and
// I am not proud, but it does at least get reports working again.
$pcre_pattern = '/\&\$_o/'; // jrs
$pcre_replace = '\$_o'; // jrs
$sRst = preg_replace ($pcre_pattern,$pcre_replace,$sRst); // jrs
$pcre_pattern = '/\&\$o/'; // jrs
$pcre_replace = '\$o'; // jrs
$sRst = preg_replace ($pcre_pattern,$pcre_replace,$sRst); // jrs
// print $sRst; //jrs debug

This changes the function calls eval()’ed in $sRst from pass-by-reference – which looks like myFunc(&$variable) – to pass-by-value – which looks like myFunc($variable). Simple stuff, and somebody should REALLY chase down the actual code which POPULATES $sRst in the first place and fix it in phpreportmaker, but for the moment, this was enough to get reports working again in my SI.

Hope this helped somebody else.

Reshuffling pool storage on the fly

If you’re new here:

Sanoid is an open-source storage management project, built on top of the OpenZFS filesystem and Linux KVM hypervisor, with the aim of providing affordable, open source, enterprise-class hyperconverged infrastructure. Most of what we’re talking about today boils down to “managing ZFS storage” – although Sanoid’s replication management tool Syncoid does make the operation a lot less complicated.

Recently, I deployed two Sanoid appliances to a new customer in Raleigh, NC.

When the customer specced out their appliances, their plan was to deploy one production server and one offsite DR server – and they wanted to save a little money, so the servers were built out differently. Production had two SSDs and six conventional disks, but offsite DR just had eight conventional disks – not like DR needs a lot of IOPS performance, right?

Well, not so right. When I got onsite, I discovered that the “disaster recovery” site was actually a working space, with a mission critical server in it, backed up only by a USB external disk. So we changed the plan: instead of a production server and an offsite DR server, we now had two production servers, each of which replicated to the other for its offsite DR. This was a big perk for the customer, because the lower-specced “DR” appliance still handily outperformed their original server, as well as providing ZFS and Sanoid’s benefits of rolling snapshots, offsite replication, high data integrity, and so forth.

But it still bothered me that we didn’t have solid state in the second suite.

The main suite had two pools – one solid state, for boot disks and database instances, and one rust, for bulk storage (now including backups of this suite). Yes, our second suite was performing better now than it had been on their original, non-Sanoid server… but they had a MySQL instance that tended to be noticeably slow on inserts, and the desire to put that MySQL instance on solid state was just making me itch. Problem is, the client was 250 miles away, and their Sanoid Standard appliance was full – eight hot-swap bays, each of which already had a disk in it. No more room at the inn!

We needed minimal downtime, and we also needed minimal on-site time for me.

You can’t remove a vdev from an existing pool, so we couldn’t just drop the existing four-mirror pool to a three-mirror pool. So what do you do? We could have stuffed the new pair of SSDs somewhere inside the case, but I really didn’t want to give up the convenience of externally accessible hot swap bays.

So what do you do?

In this case, what you do – after discussing all the pros and cons with the client decision makers, of course – is you break some vdevs. Our existing pool had four mirrors, like this:

	NAME                              STATE     READ WRITE CKSUM
	data                              ONLINE       0     0     0
	  mirror-0                        ONLINE       0     0     0
	    wwn-0x50014ee20b8b7ba0-part3  ONLINE       0     0     0
	    wwn-0x50014ee20be7deb4-part3  ONLINE       0     0     0
	  mirror-1                        ONLINE       0     0     0
	    wwn-0x50014ee261102579-part3  ONLINE       0     0     0
	    wwn-0x50014ee2613cc470-part3  ONLINE       0     0     0
	  mirror-2                        ONLINE       0     0     0
	    wwn-0x50014ee2613cfdf8-part3  ONLINE       0     0     0
	    wwn-0x50014ee2b66693b9-part3  ONLINE       0     0     0
          mirror-3                        ONLINE       0     0     0
            wwn-0x50014ee20b9b4e0d-part3  ONLINE       0     0     0
            wwn-0x50014ee2610ffa17-part3  ONLINE       0     0     0

Each of those mirrors can be broken, freeing up one disk – at the expense of removing redundancy on that mirror, of course. At first, I thought I’d break all the mirrors, create a two-mirror pool, migrate the data, then destroy the old pool and add one more mirror to the new pool. And that would have worked – but it would have left the data unbalanced, so that the majority of reads would only hit two of my three mirrors. I decided to go for the cleanest result possible – a three mirror pool with all of its data distributed equally across all three mirrors – and that meant I’d need to do my migration in two stages, with two periods of user downtime.

First, I broke mirror-0 and mirror-1.

I detached a single disk from each of my first two mirrors, then cleared its ZFS label afterward.

    root@client-prod1:/# zpool detach wwn-0x50014ee20be7deb4-part3 ; zpool labelclear wwn-0x50014ee20be7deb4-part3
    root@client-prod1:/# zpool detach wwn-0x50014ee2613cc470-part3 ; zpool labelclear wwn-0x50014ee2613cc470-part3

Now mirror-0 and mirror-1 are in DEGRADED condition, as is the pool – but it’s still up and running, and the users (who are busily working on storage and MySQL virtual machines hosted on the Sanoid Standard appliance we’re shelled into) are none the wiser.

Now we can create a temporary pool with the two freed disks.

We’ll also be sure to set compression on by default for all datasets created on or replicated onto our new pool – something I truly wish was the default setting for OpenZFS, since for almost all possible cases, LZ4 compression is a big win.

    root@client-prod1:/# zpool create -o ashift=12 tmppool mirror wwn-0x50014ee20be7deb4-part3 wwn-0x50014ee2613cc470-part3
    root@client-prod1:/# zfs set compression=lz4 tmppool

We haven’t really done much yet, but it felt like a milestone – we can actually start moving data now!

Next, we use Syncoid to replicate our VMs onto the new pool.

At this point, these are still running VMs – so our users won’t see any downtime yet. After doing an initial replication with them up and running, we’ll shut them down and do a “touch-up” – but this way, we get the bulk of the work done with all systems up and running, keeping our users happy.

    root@client-prod1:/# syncoid -r data/images tmppool/images ; syncoid -r data/backup tmppool/backup

This took a while, but I was very happy with the performance – never dipped below 140MB/sec for the entire replication run. Which also strongly implies that my users weren’t seeing a noticeable amount of slowdown! This initial replication completed in a bit over an hour.

Now, I was ready for my first little “blip” of actual downtime.

First, I shut down all the VMs running on the machine:

    root@client-prod1:/# virsh shutdown suite100 ; virsh shutdown suite100-mysql ; virsh shutdown suite100-openvpn
    root@client-prod1:/# watch -n 1 virsh list

As soon as virsh list showed me that the last of my three VMs were down, I ctrl-C’ed out of my watch command and replicated again, to make absolutely certain that no user data would be lost.

    root@client-prod1:/# syncoid -r data/images tmppool/images ; syncoid -r data/backup tmppool/backup

This time, my replication was done in less than ten seconds.

Doing replication in two steps like this is a huge win for uptime, and a huge win for the users – while our initial replication needed a little more than an hour, the “touch-up” only had to copy as much data as the users could store in a few moments, so it was done in a flash.

Next, it’s time to rename the pools.

Our system expects to find the storage for its VMs in /data/images/VMname, so for minimum downtime and reconfiguration, we’ll just export and re-import our pools so that it finds what it’s looking for.

    root@client-prod1:/# zpool export data ; zpool import data olddata 
    root@client-prod1:/# zfs set mountpoint=/olddata/images/qemu olddata/images/qemu ; zpool export olddata

Wait, what was that extra step with the mountpoint?

Sanoid keeps the virtual machines’ hardware definitions on the zpool rather than on the root filesystem – so we want to make sure our old pool’s ‘qemu’ dataset doesn’t try to automount itself back to its original mountpoint, /etc/libvirt/qemu.

    root@client-prod1:/# zpool export tmppool ; zpool import tmppool data
    root@client-prod1:/# zfs set mountpoint=/etc/libvirt/qemu data/images/qemu

OK, at this point our original, degraded zpool still exists, intact, as an exported pool named olddata; and our temporary two disk pool exists as an active pool named data, ready to go.

After less than one minute of downtime, it’s time to fire up the VMs again.

    root@client-prod1:/# virsh start suite100 ; virsh start suite100-mysql ; virsh start suite100-openvpn

If anybody took a potty break or got up for a fresh cup of coffee, they probably missed our first downtime window entirely. Not bad!

Time to destroy the old pool, and re-use its remaining disks.

After a couple of checks to make absolutely sure everything was working – not that it shouldn’t have been, but I’m definitely of the “measure twice, cut once” school, especially when the equipment is a few hundred miles away – we’re ready for the first completely irreversible step in our eight-disk fandango: destroying our original pool, so that we can create our final one.

    root@client-prod1:/# zpool destroy olddata
    root@client-prod1:/# zpool create -o ashift=12 newdata mirror wwn-0x50014ee20b8b7ba0-part3 wwn-0x50014ee261102579-part3
    root@client-prod1:/# zpool add -o ashift=12 newdata mirror wwn-0x50014ee2613cfdf8-part3 wwn-0x50014ee2b66693b9-part3
    root@client-prod1:/# zpool add -o ashift=12 newdata mirror wwn-0x50014ee20b9b4e0d-part3 wwn-0x50014ee2610ffa17-part3
    root@client-prod1:/# zfs set compression=lz4 newdata

Perfect! Our new, final pool with three mirrors is up, LZ4 compression is enabled, and it’s ready to go.

Now we do an initial Syncoid replication to the final, six-disk pool:

    root@client-prod1:/# syncoid -r data/images newdata/images ; syncoid -r data/backup newdata/backup

About an hour later, it’s time to shut the VMs down for Brief Downtime Window #2.

    root@client-prod1:/# virsh shutdown suite100 ; virsh shutdown suite100-mysql ; virsh shutdown suite100-openvpn
    root@client-prod1:/# watch -n 1 virsh list

Once our three VMs are down, we ctrl-C out of ‘watch’ again, and…

Time for our final “touch-up” re-replication:

    root@client-prod1:/# syncoid -r data/images newdata/images ; syncoid -r data/backup newdata/backup

At this point, all the actual data is where it should be, in the right datasets, on the right pool.

We fix our mountpoints, shuffle the pool names, and fire up our VMs again:

    root@client-prod1:/# zpool export data ; zpool import data tmppool 
    root@client-prod1:/# zfs set mountpoint=/tmppool/images/qemu olddata/images/qemu ; zpool export tmppool
    root@client-prod1:/# zpool export newdata ; zpool import newdata data
    root@client-prod1:/# zfs set mountpoint=/etc/libvirt/qemu data/images/qemu
    root@client-prod1:/# virsh start suite100 ; virsh start suite100-mysql ; virsh start suite100-openvpn

Boom! Another downtime window over with in less than a minute.

Our TOTAL elapsed downtime was less than two minutes.

At this point, our users are up and running on the final three-mirror pool, and we won’t be inconveniencing them again today. Again we do some testing to make absolutely certain everything’s fine, and of course it is.

The very last step: destroying tmppool.

    root@client-prod1:/# zpool destroy tmppool

That’s it; we’re done for the day.

We’re now up and running on only six total disks, not eight, which gives us the room we need to physically remove two disks. With those two disks gone, we’ve got room to slap in a pair of SSDs for a second pool with a solid-state mirror vdev when we’re (well, I’m) there in person, in a week or so. That will also take a minute or less of actual downtime – and in that case, the preliminary replication will go ridiculously fast too, since we’ll only be moving the MySQL VM (less than 20G of data), and we’ll be writing at solid state device speeds (upwards of 400MB/sec, for the Samsung Pro 850 series I’ll be using).

None of this was exactly rocket science. So why am I sharing it?

Well, it’s pretty scary going in to deliberately degrade a production system, so I wanted to lay out a roadmap for anybody else considering it. And I definitely wanted to share the actual time taken for the various steps – I knew my downtime windows would be very short, but honestly I’d been a little unsure how the initial replication would go, given that I was deliberately breaking mirrors and degrading arrays. But it went great! 140MB/sec sustained throughput makes even pretty substantial tasks go by pretty quickly – and aside from the two intervals with a combined downtime of less than two minutes, my users never even noticed anything happening.

Closing with a plug: yes, you can afford it.

If this kind of converged infrastructure (storage and virtualization) management sounds great to you – high performance, rapid onsite and offsite replication, nearly zero user downtime, and a whole lot more – let me add another bullet point: low cost. Getting started isn’t prohibitively expensive.

Sanoid appliances like the ones we’re describing here – including all the operating systems, hardware, and software needed to run your VMs and manage their storage and automatically replicate them both on and offsite – start at less than $5,000. For more information, send us an email, or call us at (803) 250-1577.

libguestfs0 and ZFS on Linux in Ubuntu

Trying to get Kimchi installed this morning, I ran into a roadblock almost immediately: libguestfs-tools depends on libguestfs0, which, on Ubuntu at least, stupidly has a hard dependency on zfs-fuse. Which is a dead project, and which conflicts with zfsutils.

In the real world, you might want libguestfs-tools without ever wanting the first thing to do with ANY form of zfs, so this dependency is a really bad idea. Even if you DO want to use libguestfs-tools WITH zfs, it’s an incredibly bad idea because zfsutils – part of ZFS on Linux – provides all the functionality needed already. Unfortunately, the package maintainers don’t seem to quite understand the issues here – I’m guessing none of them are ZFS people – so that leaves you with the need to edit the dependencies yourself.

Luckily, that’s not too hard. First, you’ll need a script, which we’ll call debedit:

#!/bin/bash

EDITOR=nano

if [[ -z "$1" ]]; then
  echo "Syntax: $0 debfile"
  exit 1
fi

DEBFILE="$1"
TMPDIR=`mktemp -d /tmp/deb.XXXXXXXXXX` || exit 1
OUTPUT=`basename "$DEBFILE" .deb`.modified.deb

if [[ -e "$OUTPUT" ]]; then
  echo "$OUTPUT exists."
  rm -r "$TMPDIR"
  exit 1
fi

dpkg-deb -x "$DEBFILE" "$TMPDIR"
dpkg-deb --control "$DEBFILE" "$TMPDIR"/DEBIAN

if [[ ! -e "$TMPDIR"/DEBIAN/control ]]; then
  echo DEBIAN/control not found.

  rm -r "$TMPDIR"
  exit 1
fi

CONTROL="$TMPDIR"/DEBIAN/control

MOD=`stat -c "%y" "$CONTROL"`
$EDITOR "$CONTROL"

if [[ "$MOD" == `stat -c "%y" "$CONTROL"` ]]; then
  echo Not modfied.
else
  echo Building new deb...
  dpkg -b "$TMPDIR" "$OUTPUT"
fi

rm -r "$TMPDIR"

Save that, name it debedit, and chmod 755 it.

Now, you’ll need to download libguestfs0, which is the package that has the bad dependencies, which you’ll edit:

you@box:~$ apt-get download libguestfs0
you@box:~$ ./debedit libguest*deb

Remove the zfs-fuse dependency from the Depends: line in the deb file, and exit nano. Finally, install your modified libguestfs0 package:

you@box:~$ sudo dpkg -i *modified.deb ; sudo apt-get -f install

All done! At least, until and unless the next update to libguestfs0 downloads and attempts to install a new .deb that wants to put that dependency right back again, in which case you’ll need to lather-rinse-repeat.

I me-too’ed an existing bug at https://bugs.launchpad.net/ubuntu/+source/libguestfs/+bug/1053911 ; if you’re affected, you probably should too.

ZFS compression: yes, you want this

So ZFS dedup is a complete lose. What about compression?

Compression is a hands-down win. LZ4 compression should be on by default for nearly anything you ever set up under ZFS. I typically have LZ4 on even for datasets that will house database binaries… yes, really. Let’s look at two quick test runs, on a Xeon E3 server with 32GB ECC RAM and a pair of Samsung 850 EVO 1TB disks set up as a mirror vdev.

This is an inline compression torture test: we’re reading pseudorandom data (completely incompressible) and writing it to an LZ4 compressed dataset.

root@lab:/data# pv < in.rnd > incompressible/out.rnd
7.81GB 0:00:22 [ 359MB/s] [==================================>] 100%

root@lab:/data# zfs get compressratio data/incompressible
NAME                 PROPERTY       VALUE  SOURCE
data/incompressible  compressratio  1.00x  -

359MB/sec write… yyyyyeah, I’d say LZ4 isn’t hurting us too terribly here – and this is a worst case scenario. What about something a little more realistic? Let’s try again, this time with a raw binary of my Windows Server 2012 R2 “gold” image (the OS is installed and Windows Updates are applied, but nothing else is done to it):

root@lab:/data/test# pv < win2012r2-gold.raw > realworld/win2012r2-gold.out
8.87GB 0:00:17 [ 515MB/s] [==================================>] 100%

Oh yeah – 515MB/sec this time. Definitely not hurting from using our LZ4 compression. What’d we score for a compression ratio?

root@lab:/data# zfs get compressratio data/realworld
NAME            PROPERTY       VALUE  SOURCE
data/realworld  compressratio  1.48x  -

1.48x sounds pretty good! Can we see some real numbers on that?

root@lab:/data# ls -lh /data/realworld/win2012r2-gold.raw
-rw-rw-r-- 1 root root 8.9G Feb 24 18:01 win2012r2-gold.raw
root@lab:/data# du -hs /data/realworld
6.2G	/data/realworld

8.9G of data in 6.2G of space… with sustained writes of 515MB/sec.

What if we took our original 8G of incompressible data, and wrote it to an uncompressed dataset?

root@lab:/data#  zfs create data/uncompressed
root@lab:/data# zfs set compression=off data/uncompressed
root@lab:/data# cat 8G.in > /dev/null ; # this is to make sure our source data is preloaded in the ARC
root@lab:/data# pv < 8G.in > uncompressed/8G.out
7.81GB 0:00:21 [ 378MB/s] [==================================>] 100% 

So, our worst case scenario – completely incompressible data – means a 5% performance hit, and a more real-world-ish scenario – copying a Windows Server installation – means a 27% performance increase. That’s on fast solid state, of course; the performance numbers will look even better on slower storage (read: spinning rust), where even worst-case writes are unlikely to slow down at all.

Yep, that’s a win.

ZFS dedup: tested, found wanting

Even if you have the RAM for it (and we’re talking a good 6GB or so per TB of storage), ZFS deduplication is, unfortunately, almost certainly a lose.

I don’t usually have that much RAM to spare, but one server has 192GB of RAM and only a few terabytes of storage – and it stores a lot of VM images, with obvious serious block-level duplication between images. Dedup shows at 1.35+ on all the datasets, and would be higher if one VM didn’t have a couple of terabytes of almost dup-free data on it.

That server’s been running for a few years now, and nobody using it has complained. But I was doing some maintenance on it today, splitting up VMs into their own datasets, and saw some truly abysmal performance.

root@virt0:/data/images# pv < jabberserver.qcow2 > jabber/jabberserver.qcow2 206MB 0:00:31 [7.14MB/s] [>                  ]  1% ETA 0:48:41

7MB/sec? UGH! And that’s not even a sustained average; that’s just where it happened to be when I killed the process. This server should be able to sustain MUCH better performance than that, even though it’s reading and writing from the same pool. So I checked, and saw that dedup was on:

root@virt0:~# zpool list
NAME   SIZE  ALLOC   FREE    CAP  DEDUP  HEALTH  ALTROOT
data  7.06T  2.52T  4.55T    35%  1.35x  ONLINE  -

In theory, you’d think that dedup would help tremendously with exactly this operation: copying a quiesced VM from one dataset to another on the same pool. There’s no need for a single block of data to be rewritten, just more pointers added to the metadata for the existing blocks. However, dedup looked like the obvious culprit for my performance woes here, so I disabled it and tried again:

root@virt0:/data/images# pv < jabberserver.qcow2 > jabber/jabberserver.qcow219.2GB 0:04:58 [65.7MB/s] [============>] 100%

Yep, that’s more like it.

TL;DR: ZFS dedup sounds like a great idea, but in the real world, it sucks. Even on a machine built to handle it. Even on exactly the kind of storage (a bunch of VMs with similar or identical operating systems) that seems tailor-made for it. I do not recommend its use for pretty much any conceivable workload.

(On the other hand, LZ4 compression is an unqualified win.)

Exploring copyleft

Recently, the ElementaryOS Linux distribution screwed the pooch pretty badly, PR-wise – TL;DR if ElementaryOS nerfs the blog post they never should have made: “if you don’t donate money when you download ElementaryOS, you’re a cheater”. Yeah, that didn’t go down so well.

Where it got interesting was this Reddit thread discussing the gaffe. It never ceases to amaze me what a slippery concept copyleft really is, and how easy it is for people to get confused in discussing it and its ramifications. In case it isn’t obvious, by the way, I’m “people” too, so I’m going to revisit copyleft here with lots of citations for my own benefit as well as that of anyone reading. There are quite a lot of copyleft licenses, but I’m going to look at the GPLv3 (at this time, the most current version). I promise not to cite any other copyleft license in this article, and if I want to talk about a different one (GPLv2, Affero GPL, Lesser GPL, etc) I’ll make it perfectly clear when (if) I am.

First Things First

The first first thing – gnu.org itself provides a quick guide to the GPLv3. There is also a GPL FAQ that covers all the GPL licenses, not just GPLv3. There are instructions for the usage of the GPL licenses so you know how to make sure a license you’ve selected is properly applied to your work. And finally, and most authoritatively (this is the part which might be judged in a court of law; the rest is helpful but not legally binding), here is the gnu.org hosted authoritative copy of the most current version of the GPL itself – at the time of this writing, GPLv3. (There is sadly no way to specifically link to GPLv3 at gnu.org right now – the “old licenses” area direct links to old versions by specific number, but treats the GPLv3 as though there will never, ever be further alterations to the main GPL. Time will tell, I suppose.)

I will be citing the GPLv3’s terms directly from the version at gnu.org as linked above. I’m not going to attempt to move line by line through the entire license; if you want to do that, you should read the entire license yourself. Instead, I’m going to be moving through the basic rights afforded by copyleft and how they affect the usage, distribution, and commercial value of GPLv3 licensed software, referring to the GPLv3 directly where appropriate.

I am not a lawyer. I am not your lawyer. I am just a reasonably bright person who is reading through the text of the GPLv3, and interpreting it with the assistance of a couple of decades of industry experience and conversations with other reasonably bright people – some lawyers, some not – about what the license is intended to do, and what it actually does.

About the Preamble…

The GPL in general is a very unusual legal document – not specifically in terms of the rights it offers, and certainly not in terms of its complexity (there are contracts you wouldn’t want to carry around printed out without a wheelbarrow; no version of the GPL is one of them), but in terms of the fact that Richard Stallman is not a lawyer, but is an extremely, extremely stubborn man who will listen to advice from lawyers, but not take it.

It is incredibly abnormal for a legal contract to try to give you a plain English overview of what its actual goals, and put said overview in the legally binding contract itself. That’s incredibly abnormal because legal contracts are much like source code – comments may be helpful to you, but the compiler ignores them, and the computer itself is only “bound” by the actual instructions. RMS, for better or for worse, decided that was bullshit and declared that his Preamble is part of the contract itself. This frustrates the living hell out of actual lawyers, because – just like the comments in source code – text that humans in general find “easy reading” tend to be abstract, vague, and difficult to concretely interpret in predictable, enforceable ways. That’s why they’re comments, and why the actual executable instructions are there – if they were equivalent, we’d just write the comments and not bother with the code!

I’m not going to go into the Preamble here – it’s just a summary of the general idea of the license itself – but it’s probably worth realizing that technically, at least in theory, it’s legally enforceable. God help us all, lawyers, internet lawyers, and little fishies alike.

Your Copyleft Rights

Rather than trying to abstract the Preamble any further than it already is, I’m going to reproduce the list of basic rights which is laid out in gnu.org’s Quick Guide to the GPL linked above. While not a part of the license itself, understanding them is key to understanding why the license says the things it does, and what it’s trying to accomplish.

Nobody should be restricted by the software they use. There are four freedoms that every user should have:

  • the freedom to use the software for any purpose,
  • the freedom to change the software to suit your needs,
  • the freedom to share the software with your friends and neighbors, and
  • the freedom to share the changes you make.

When a program offers users all of these freedoms, we call it free software.

The GPL sets out to accomplish the granting of these basic freedoms almost entirely by governing the distribution of source code. (If you aren’t sure what source code, object code, or binaries are – or the differences between them – please visit the text of the GPL itself. There’s a Terms and Definitions section at the top.)

When aren’t you bound by the GPL?

This is a pretty key component to the GPL that most people don’t understand. Without distribution, it’s basically impossible to violate the GPL. You can modify GPL licensed code without having to give it to the whole world. The catch happens the first time you want somebody else to use your code – when you distribute the code to that other person, your GPL obligations kick in, and now you have to make the source code available.

Let’s look at the relevant bit from the GPLv3 itself:

You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.

Want to make your own super special fork of the Linux kernel that nobody gets to use but you, ’cause you’re so awesome? You can do that.

Own a company, and want to build your own super special private Linux kernel in-house, and have your staff of minions do the work, and your employees use workstations and servers running that kernel? You can do that.

Want to sell – or even give away – your new Awesum Kernel Terbo 2000? Welp, now your GPL obligations kick in. You can do that, but now you’re going to be bound by the conditions of the GPL, as we see in the very next line:

Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.

Conditions/Obligations For Distribution

OK, here’s where we get into the actual viral license and copyleft and enforcing your freedoms and all that other good stuff.

Here’s the basic overview:

  • You may distribute your code, source or otherwise, to anybody you want to, at any price or none
  • Anyone who you have distributed code to, you must also offer source code to, at no additional charge
  • Anyone who you have distributed code to has the same rights, obligations, and conditions to that code that you did to begin with

Judging by the Reddit thread that started all this, there seems to be some contention on this point. So let’s dive into the license.

5. Conveying Modified Source Versions.

You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:

(The ways boil down to “the ways you’d normally get a copy of source code, if you intended to actually read, modify, compile, and/or further distribute it yourself.”)

This one, honestly, is a bit of a brain-bender, and I’ll admit I had some misconceptions about it until reading and re-reading and vacuuming the steam off of my skull afterwards. It’s very obvious that the GPL requires you to convey source, but does not require you to convey compiled code in any form. What’s easy to miss, in true Purloined Letter fashion, is that very first part of the sentence: “You may convey a covered work in object code form.”

So if – for example – you wanted to give somebody a copy of an RHEL install ISO, you could do that, even if you hadn’t compiled the code yourself. (You would be obligated to provide the full source code to whoever you gave the copy of the object code to if they asked for it, of course.)

That isn’t necessarily a useful right, though, which we’ll cover next.

Thinking several moves ahead

OK, so we’ve looked at enough of the GPL to understand the general thrust. (We skipped the patent section in the GPLv3, but for my purposes here, it’s enough to say “it’s there, and it tries to indemnify users from patent abuse in the same ways that the GPL has always tried to indemnify them from copyright abuse.”)

But what about loopholes and ramifications? This is a license that fits in fewer than ten generously-margined pages, even with headers and footers and website fluff prepended and appended; it doesn’t really attempt to and can’t possibly specifically try to chink or even specify every possible loophole or ramification. So you need to do a little of it yourself, especially if you’re going to play the ever-popular sport of internet lawyer – or put your own money on the line.

Can people just make copies of your actual binaries?

Yes, they can – it’s easy to miss, but the GPL gives them the right to copy your actual binaries, not just your source code. The more interesting question here is “does that matter?” and the answer is “not really.” If you want people to have to compile their own binaries rather than redistributing yours, the GPL does nothing to keep you from littering the source with boobytraps – effectively if not literally. Want to put in a routine that checks for a support contract and immediately refuses to continue if none is found? That’s perfectly legit, and it will keep people from just using your binaries. It won’t, however, keep somebody from modifying your source, stripping out the unwanted routine, and then themselves compiling and distributing that.

Obfuscating the source to try to keep people from dyking out your CheckCustomerStatus() routine isn’t an option, either:

“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.

If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information.

This section could frankly be better written, but basically if you make it impossible to dyke unpleasant things out of your code, you’re just going to have to give people support in dyking them out anyway.

The TL;DR on this one is you can effectively prevent people from distributing your binaries – but if you do, one of two things will happen. One, somebody sets up shop modifying, compiling, and distributing your code without the unpleasant bits in it, and now people get binaries and source from them, not from you. Or two, your project just isn’t interesting enough for anyone to bother with it if you’re going to be unpleasant about it, and it therefore dies on the vine.

An excellent example of this playing out in real life is Red Hat Enterprise Linux and CentOS. CentOS is a RHEL clone. CentOS is largely if not entirely compiled from source, rather than merely being copies of the RHEL binaries, because there are enough instances of RHEL referring to resources that are only available to Red Hat customers that it’s better to do it that way even though it’s a pain in the butt. I can tell you informally and you’ll-just-have-to-believe-me-but-it-shouldn’t-be-difficult that the relationship between Red Hat and CentOS was a little strained. Nobody would say anything bad about CentOS, publicly or for the most part even privately on the Red Hat campus, but it’s hard not to instinctively feel “those guys are stealing our customers.” But the official position was that a CentOS user wasn’t a RHEL customer – one of the most important selection criteria for a RHEL customer being “someone who wants to give us money.” This was a very wise position, IMO. In the end, Red Hat actually acquired CentOS itself – not to kill it on the vine, but to make sure it was being run well, and reap the PR benefits of running it well.

Red Hat is pretty awesome.

What if I sell one single copy of my GPL program, but then four billion people demand the source code?

This is a pretty reasonable fear; there’s a significant cost associated with distributing a program to thousands, hundreds of thousands, millions, or maybe even more people. “Internet scale” is thrilling, but it can be scary! But relax – remember the bit about how the GPL’s terms only kick in on distribution, and only grant rights to the person you distributed a covered work to? The rest of the world didn’t buy (or otherwise acquire) a copy of your program, so you don’t owe them jack in terms of copies of the source code.

The very first customer you sell to might decide to offer your source code to all those people, of course, with or without your permission. But you won’t be forced to do it yourself.

Let’s take this the next logical step, though: if your program is worthwhile, somebody will want to distribute it. If that person is you, you stand a chance to reap some benefit from it – that could be literal money in the form of sales, donations, or support contracts. Or it could be more immaterial, in the form of fame, respect, job offers, consulting gigs. But if you aren’t the one doing the distribution, it’s going to be less likely for you to be the one reaping the reward. So you should probably plan on distributing your code to all who ask rather than only to those who purchased it, even though you don’t actually have to. (See again: Red Hat eventually deciding to acquire CentOS… and continue their operation.)

What if I change my mind about GPL? Can the cat be put back into the bag?

Well, yes and no. You can always relicense your own code, but:

  • Anyone who’s already acquired it from you under the GPL will still have their own rights of modification and redistribution
  • If you’ve ever accepted contributions from community developers, you won’t have the rights to relicense them unless you suckered those developers into specifically signing full – not merely GPL – rights to their own contributions to you
  • Information wants to be free.

In theory, you could release your awesome project, only sell it to five people, never let anyone else contribute to your own copy of it, never have anyone else fork it, outlive those original five people, and then relicense it proprietary. Mwahahaha! In practice, if your project ever got any market penetration or adoption at all, that’s never going to happen, even if you never let anyone else contribute to the codebase. Somebody out there is still going to have a copy, have distribution rights, and if there’s a significant group of people wanting it, they’ll distribute it. If there’s not a significant market for the project, then why worry about all this anyway?

If you’ve never accepted code contributions from anyone else, you can always distribute your work under any license you see fit, regardless of what licenses you’ve distributed it under before. The question quickly reduces to “does that matter?”, though, since anyone who ever received it from you under the GPL can themselves distribute it to whoever they like, and you have no right to prevent them from doing it.

Taking this a step further, what if you wrote a few thousand more lines of code adding lots of awesome new features and you don’t want to release that under the GPL, you want it to be proprietary? Yep, you can do that – again assuming you are the only author and/or all actual authors of all of the original code that’s still a part of YourProject 2.0 have granted you full control of the license – but you better think about that decision long and hard. If you’re the sole author, you have the freedom to stop distributing under the GPL – but everybody you’ve ever distributed to, and everybody they’ve ever distributed to, and so forth down the line equally have the freedom to just stop getting code from you. You might easily discover that nobody wants to buy your new v2.0 with the onerous license – now they prefer to get the v1.0 licensed GPL, and a community has sprung up around that codebase, and the next thing you know more new features and bugfixes and sweet, sweet PR is going to your “old and busted” codebase that you no longer really control, rather than the new and supposedly awesome one you’re sitting on.

Final Conclusions

  • The GPL is awesome. Don’t fear it, embrace it.
  • You can try to put the cat back in the bag, but don’t be surprised when the cat doesn’t actually go.
  • If your customers want to do business with you, there’s money to be made.
  • If your customers don’t want to do business with you, they aren’t – and won’t be – your customers.

A lot of these lessons are ultimately true for all business, not just FOSS business, of course. The GPL just forces you to realize them more quickly, and works harder to keep you from exploiting your way around it temporarily.