Skip to content

Commit

Permalink
Merge branch 'release/0.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Dahlgren committed Feb 20, 2014
2 parents 010ce7a + 4f382e5 commit 384e000
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 10 deletions.
23 changes: 19 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,34 @@ To stop creating automated backups for a volume, run this:
Creating snapshots
------------------

Now, to start taking snapshots you will need to have Automated EBS Snapshots
running.
Now, to start taking snapshots you will need to have Automated EBS Snapshots running. You can either run ``automated-ebs-snapshots`` manually (i.e. scheduled in crontab or such) or have it running in daemon mode.

Manual execution
^^^^^^^^^^^^^^^^
Running ``automated-ebs-snapshots`` manually:
::

automated-ebs-snapshots --config ~/automated-ebs-snapshots.conf --run

It will check if there are any volumes with no or too old snapshots. New
snapshots will be created if needed.

There is no daemon mode currently, so you will need to run something like this.
Daemon mode
^^^^^^^^^^^
Start the daemon by running
::

automated-ebs-snapshots --config ~/automated-ebs-snapshots.conf --deamon start

Stop the daemon with
::

automated-ebs-snapshots --config ~/automated-ebs-snapshots.conf --deamon stop

You can also restart it using
::

nohup while true ; do automated-ebs-snapshots --config ~/automated-ebs-snapshots.conf --run >> /var/log/automated-ebs-snapshots.log 2>&1 ; sleep 600 ; done
automated-ebs-snapshots --config ~/automated-ebs-snapshots.conf --deamon restart

Author
------
Expand Down
75 changes: 70 additions & 5 deletions automated_ebs_snapshots/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
""" Automatic AWS EBS snapshot handling """
import logging
import logging.config
import time
import sys

from automated_ebs_snapshots.command_line_options import args
from automated_ebs_snapshots import config_file_parser
from automated_ebs_snapshots import connection_manager
from automated_ebs_snapshots import snapshot_manager
from automated_ebs_snapshots import volume_manager
from automated_ebs_snapshots.daemon import Daemon

logging.config.dictConfig({
'version': 1,
Expand All @@ -23,30 +26,38 @@
'class': 'logging.StreamHandler',
'formatter': 'standard'
},
'file': {
'level': 'DEBUG',
'class': 'logging.handlers.TimedRotatingFileHandler',
'formatter': 'standard',
'filename': args.log_file,
'when': 'midnight',
'backupCount': 5
}
},
'loggers': {
'': {
'handlers': ['default'],
'handlers': ['default', 'file'],
'level': 'INFO',
'propagate': True
},
'lib.bundle_manager': {
'handlers': ['default'],
'handlers': ['default', 'file'],
'level': 'DEBUG',
'propagate': False
},
'lib.config_handler': {
'handlers': ['default'],
'handlers': ['default', 'file'],
'level': 'DEBUG',
'propagate': False
},
'lib.connection_handler': {
'handlers': ['default'],
'handlers': ['default', 'file'],
'level': 'DEBUG',
'propagate': False
},
'lib.deployment_manager': {
'handlers': ['default'],
'handlers': ['default', 'file'],
'level': 'DEBUG',
'propagate': False
}
Expand All @@ -55,6 +66,39 @@
logger = logging.getLogger(__name__)


class AutoEBSDaemon(Daemon):
""" Daemon for automatic-ebs-snapshots"""

def run(self, check_interval=300):
""" Run the daemon
:type check_interval: int
:param check_interval: Delay in seconds between checks
"""
while True:
# Read configuration from the config file if present, else fall
# back to command line options
if args.config:
config = config_file_parser.get_configuration(args.config)
access_key_id = config['access-key-id']
secret_access_key = config['secret-access-key']
region = config['region']
else:
access_key_id = args.access_key_id
secret_access_key = args.secret_access_key
region = args.region

# Connect to AWS
connection = connection_manager.connect_to_ec2(
region, access_key_id, secret_access_key)

snapshot_manager.run(connection)

logger.info('Sleeping {} seconds until next check'.format(
check_interval))
time.sleep(check_interval)


def main():
""" Main function """
# Read configuration from the config file if present, else fall back to
Expand All @@ -69,6 +113,27 @@ def main():
secret_access_key = args.secret_access_key
region = args.region

if args.daemon:
pid_file = '/tmp/automatic-ebs-snapshots.pid'
daemon = AutoEBSDaemon(pid_file)

if args.daemon == 'start':
daemon.start()

elif args.daemon == 'stop':
daemon.stop()
sys.exit(0)

elif args.daemon == 'restart':
daemon.restart()

elif args.daemon in ['foreground', 'fg']:
daemon.run()

else:
print 'Valid options for --daemon are start, stop and restart'
sys.exit(1)

# Connect to AWS
connection = connection_manager.connect_to_ec2(
region, access_key_id, secret_access_key)
Expand Down
9 changes: 9 additions & 0 deletions automated_ebs_snapshots/command_line_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@
'--version',
action='count',
help='Print the Automated EBS Snapshots version and exit')
general_ag.add_argument(
'--log-file',
default='/var/log/automated-ebs-snapshots.log',
help='Path to the log file. Default: /var/log/automated-ebs-snapshots.log')
general_ag.add_argument(
'--daemon',
help=(
'Run Automatic EBS Snapshots in daemon mode. Valid modes are '
'[start|stop|restart|foreground]'))
admin_actions_ag = parser.add_argument_group(
title='Administrative actions')
admin_actions_ag.add_argument(
Expand Down
137 changes: 137 additions & 0 deletions automated_ebs_snapshots/daemon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
"""
Python daemon implementation from:
http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
"""
import atexit
import os
import sys
import time
from signal import SIGTERM


class Daemon:
""" A generic daemon class
Usage: subclass the Daemon class and override the run() method
"""
def __init__(self, pidfile, stdin='/dev/null',
stdout='/dev/null', stderr='/dev/null'):
""" Constructor """
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile

def daemonize(self):
""" Do the UNIX double-fork magic
See Stevens' "Advanced Programming in the UNIX Environment" for
details (ISBN 0201563177)
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
"""
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError as err:
sys.stderr.write(
"fork #1 failed: %d (%s)\n" % (err.errno, err.strerror))
sys.exit(1)

# decouple from parent environment
os.chdir("/")
os.setsid()
os.umask(0)

# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError as err:
sys.stderr.write(
"fork #2 failed: %d (%s)\n" % (err.errno, err.strerror))
sys.exit(1)

# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = file(self.stdin, 'r')
so = file(self.stdout, 'a+')
se = file(self.stderr, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())

# write pidfile
atexit.register(self.delpid)
pid = str(os.getpid())
file(self.pidfile, 'w+').write("%s\n" % pid)

def delpid(self):
os.remove(self.pidfile)

def start(self, *args, **kwargs):
""" Start the daemon """
# Check for a pidfile to see if the daemon already runs
try:
pf = file(self.pidfile, 'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None

if pid:
message = "pidfile %s already exist. Daemon already running?\n"
sys.stderr.write(message % self.pidfile)
sys.exit(1)

# Start the daemon
self.daemonize()
self.run(*args, **kwargs)

def stop(self):
""" Stop the daemon """
# Get the pid from the pidfile
try:
pf = file(self.pidfile, 'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None

if not pid:
sys.stderr.write(
"pidfile {0} does not exist. Daemon not running?\n".format(
self.pidfile))
return # not an error in a restart

# Try killing the daemon process
try:
while 1:
os.kill(pid, SIGTERM)
time.sleep(0.1)
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
if os.path.exists(self.pidfile):
os.remove(self.pidfile)
else:
print str(err)
sys.exit(1)

def restart(self):
""" Restart the daemon """
self.stop()
self.start()

def run(self, check_interval=1):
"""
You should override this method when you subclass Daemon.
It will be called after the process has been
daemonized by start() or restart().
"""
2 changes: 1 addition & 1 deletion automated_ebs_snapshots/settings.conf
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[general]
version: 0.1.0
version: 0.2.0

0 comments on commit 384e000

Please sign in to comment.