0

I am a novice to Python and have started to write my first module which will perform a backup to external storage (typically one or more USB disks).

The desired behaviour is:

  1. check if destination (backup disk) is mounted already. destination_was_mounted becomes True or False
  2. If destination_was_mounted = False then mount the destination.
  3. If mountDestination fails, raise exception and restart loop.
  4. If mountDestination is ok, check if checkfile (and backup_dir) exists on destination.
  5. if check_destination fails raise exception and restart loop.
  6. Continue processing (have not coded this yet).
  7. Under any condition if destination_was_mounted = False then un-mount the destination.

The problem is that if the check_destination part raises an exception it fails to unmount the destination even though I have that in the finally section. It's as if the destination_was_mounted becomes True even it was supposed to be False. Or as if check_destination runs before mount_destination even though it's after it.

My references (amongst looking at the python documentation and my Learning Python book):

Python: How to tell the for loop to continue from a function?

How to retry after exception in python?

How to get back to the for loop after exception handling

#!/usr/bin/env python3.1

import sys
import os
import os.path
import subprocess
import configparser
CONFIGFILE = 'backup.ini'
# do i need this?
config = {}

config = configparser.ConfigParser()
config.read(CONFIGFILE)
backup_sources = sorted(config.sections()[1:])

class NoCheckFile(Exception):
    pass

def mountDestination(destination):
    return subprocess.check_call(['mount', destination])

def unMountDestination(destination):
    return subprocess.check_call(['umount', destination])

def checkDestination(destination, backup_dir, checkfile):
    return os.path.exists(destination + '/' + backup_dir + '/' + checkfile)

''' exception handlers '''
# perhaps add another arg like 0 or 1 for success/failure
def handleCalledProcessError(ex):
    print('Exception: ' + str(ex))

def handleNoCheckFile(ex):
    print('Exception: ' + str(ex))

# rename me once I work out logging
def logExecute(result):
    print('Info: ' + str(result))
# can I pass logging output here

def main():
    for section in backup_sources:
        item = dict(config.items(section))
        destination = item['destination']
        destination_was_mounted = os.path.ismount(destination)
        backup_dir = item['backup_dir']
        checkfile = item['checkfile']
        try:
            ''' check destination_was_mounted and mount destination if required '''
            mount_destination = None
            unmount_destination = None
            if not destination_was_mounted:
                mount_destination = mountDestination(destination)

            ''' check that checkfile exists in backup_dir '''
            check_destination = checkDestination(destination, backup_dir, checkfile)
            if not check_destination:
                raise NoCheckFile('no checkfile found')

            ''' lvm snapshot, mount and source path update '''

            ''' backup engine code here '''

            ''' check destination_was_mounted and um-mount destination if required '''
            if not destination_was_mounted:
                unmount_destination = unMountDestination(destination)
        except subprocess.CalledProcessError as ex:
            print(destination, 'mounted before loop start: ', destination_was_mounted)
            handleCalledProcessError(ex)
        except NoCheckFile as ex:
            handleNoCheckFile(ex)
        else:
            print(destination, 'mounted before loop start: ', destination_was_mounted)
            logExecute(mount_destination)
            logExecute(check_destination)
        finally:
            print('should always see me')
            logExecute(unmount_destination)
            # return to say True or False

    # this should be where email reports etc. go

if __name__ == '__main__':
    main()

The relevant parts of the backup.ini file are:

[general]

[1]
DESTINATION = /mnt/backup2
BACKUP_DIR = BACKUP2
CHECKFILE = .checkfile

[2]
DESTINATION = /mnt/backup1
BACKUP_DIR = BACKUP1
CHECKFILE = .checkfile

The output looks like this - I have 2 backup disks attached at the mount points specified in [1] and [2] and have intentionally not created the checkfile for [1] to test.

> umount /mnt/backup1
umount: /mnt/backup1: not mounted
> umount /mnt/backup2 
umount: /mnt/backup2: not mounted
> mugsyback.py 
Exception: no checkfile found
should always see me
Info: None
/mnt/backup1 mounted before loop start:  False
Info: 0
Info: True
should always see me
Info: 0
> mount
...
/dev/sdc1 on /mnt/backup2 type ext3 (rw)
Community
  • 1
  • 1
jelloir
  • 155
  • 2
  • 12

1 Answers1

0

You have the unmounting code in the try block. In finally, you're just logging.

Eryk Sun
  • 33,190
  • 5
  • 92
  • 111
  • Thanks, this is correct and works after moving the unmounting code to finally:. I also added a print to "except NoCheckFile as ex:" which helped me follow the process and renamed the logExecute function to logInfo. From the reading I was doing I had become confused about how the try: section was executed and thought else: objects were still being called after an except - newbie mistake. – jelloir Jul 10 '11 at 05:09