donatas abraitis

Handling quota notifications using netlink sockets

Do you have a project which requires quotas must be implemented correctly? What about notifying clients when they reach the limits? No.. I asked what about real-time notifications?

At Hostinger we have enabled quotas as well. Quotas work well unless there is a need to notify clients about the limits. Ok, so by default there are few options to notify clients when they hit the limits:

  • warnquota - checks the disk quota for specified local filesystems and mails a warning message to those users who have reached their soft limit;
  • quota_nld - daemon that listens on netlink socket and processes received quota warnings;
  • repquota - prints a summary of the disk usage and quotas for the specified file systems.

If we are talking about real-time-only then this list consists of a single candidate: quota_nld. I invested few hours in playing with this daemon, but it didn’t work as expected. Here are the few drawbacks:

  • Written in C - it means, it’s not so flexible as with Python to append this daemon with custom needs;
  • It doesn’t work at all. I tried to simulate almost all cases to catch those notifications - no success. I decided to do not spend more time on this daemon.

It was enough arguments to look for something more simpler. While quota_nld handles generic netlink socket messages, why not listen for those messages using Python and implement a custom logic layer on top of the engine?

After Googling a bit, I found first promising library. It would be easily extendable for quotas, but it lacked documentation as usually. I just started to define the format for quotas using this library, but stuck on how to bind to an arbitrary group.

Time to google one more time. The second time was more successful, I found pyroute2 which has quite good documentation. After some help from pyroute2 developer, I crafted this wrapper. It’s possible to super easy connect custom notification provider. For instance:

import sys
from dquota import DQuotNotifications

class DQuotNotificationProviderStdout:
    def send(self, input):
        sys.stdout.write(input)
        sys.stdout.flush()

DQuotNotifications(provider=DQuotNotificationProviderStdout()).run()

It takes input as quota’s alert and handles as you wish. In this case, just print to stdout. A more usable example would be to process all those alerts using queuing like RabbitMQ, ZeroMQ, Kafka, etc., but it’s out of the scope of this post.

Additionally, it’s even nice enough to store this information inside extended file’s attributes for ad-hoc analysis later:

    def update_metadata(self, file):
        quota_hit_count = 0
        try:
            quota_hit_count = int(xattr.getxattr(file, 'user.quota_hit_count'))
        except:
            pass

        quota_hit_count += 1
        xattr.setxattr(file, 'user.quota_hit_count', str(quota_hit_count))

Conclusion

Read two things only:
* source code
* man pages

I would sometimes rewrite this to:

Read one thing only:
* google

Read this thing once again.