-
Notifications
You must be signed in to change notification settings - Fork 86
BackupController
The BackupController is a stack. As new changes are made, you need to
instantiate a RestorableChange
and then push it onto the stack stored.
As part of both convert
and analyze
convert2rhel will make changes to the
system. For everything in analyze and for as much as possible in convert,
convert2rhel will attempt to record the state from before the changes were made
and rollback to that previous state in case of problems. To aid in that, we
have a backup.BackupController
class where we can store the old state, make
the changes, and then revert back later.
For every type of state that we change, we need to create a subclass of
backup.RestorableChange
. Whenever possible, we try to reuse a
RestorableChange
written for one section of code with a RestorableChange
that does the same thing in another section. For instance, placing TLS
certificates on the system is similar no matter what the certificate is. So we
have one cert.Cert
class which can be used from multiple places to save the
present state of the certificates and roll back to that if we need to.
Each RestorableChange
class needs to implement three methods:
-
__init__()
needs to take all of the parameters necessary for the other two methods to execute and save that information into instance variables if necessary. -
enable()
will be called when the change is added to theBackupController
. It needs toenable()
takes no parameters. Anything it needs to know must be passed to__init__()
. -
restore()
will be called when the change is started. It takes no parameters.
Each method should call the parent class's method via super()
.
This is the actual basic structure of the backup folder, where we sub-divide into small modules to keep track of different backup items.
backup/
├── certs.py # Module to keep track of backup/restore of certifications related material
├── files.py # Module to keep track of backup/restore of anything that is related to a fiel
├── __init__.py
├── packages.py # Module to keep track of backup/restore of anything that is a package (rpm) operation
1 directory, 4 files
Below, we can see a basic structure of how a RestorableBackup class can be
composed. Basically, as described in the API breakdown above, every
RestorableBackup class will need to have the enable
and restore
methods.
from convert2rhel.backup import RestorableChange
class RestorableExample(RestorableChange):
def __init__(self, ...):
"""
docstring explaining what the class do
"""
super(RestorableExample, self).__init__()
def enable(self):
"""docstring for how the backup work"""
# Prevent multiple backup
if self.enabled:
return
# Code related to backup
# Set the enabled value
super(RestorableExample, self).enable()
def restore(self):
"""docstring for how the restore work"""
if not self.enabled:
return
# Code related to restore
super(RestorableExample, self).restore()
In the enable
method, you will put all the logic behind how you want to
backup your asset. This can involve multiples steps and other functions if
needed, but in the end, the BackupController will only be able to work properly
if you use and define this method. Trying to use anything else for backup will
not be handled by the BackupController.
On the other hand, the restore
method will handle how we will restore the
asset we backed up earlier in the enable
method. This can contain any type of
logic needed by the asset to be restored to the system.
It is essential, for both of them, that you call super()
at the end of the
method for the specific base method you are executing. This will ensure to set
a couple of properties internally for the class that will help the
BackupController to control if the asset being saved or restored was already
been processed.
Backups are placed in order into a list that tracks all the RestorableChanges
derivated classes. Essentially, the backup is very simple, see the below code:
from convert2rhel.backup import backup_control
from convert2rhel.backup.files import RestorableFile
VERY_IMPORTANT_FILE_TO_BACKUP = "/etc/super-very-most-important-file.txt"
restorable_file = RestorableFile(VERY_IMPORTANT_FILE_TO_BACKUP)
backup_control.push(restorable_file)
After we pass the instance of the restorable_file to be pushed onto the list of
the BackupController, the controller will be responsible to call the enable
method inside the class that we just pushed to the stack.
In essence, after each push we do, the last item will be added to the last position of the list, becoming then a stack of items to backup.
The rollback will pick all items inside the list controlled by the
BackupController internals, revert the list, and then call the restore
method
for each item inside it.
The BackupController class contains two public methods to help us trigger the rollback for the instances we added to the stack.
The first method is the pop
, in which case will remove the last element from
the list and trigger the rollback for that item via the restore
method
contained inside the class instance.
from convert2rhel.backup import backup_control
print(len(backup_control._restorables)) # 10
backup_control.pop() # Pop the last element and trigger the `restore` method for that instance
print(len(backup_control._restorables)) # 9
The second one is the pop_all
in which it is used during the rollback phase
for convert2rhel. This method will call pop
for all elements inside the list
and trigger the rollback for each one of them.
from convert2rhel.backup import backup_control
print(len(backup_control._restorables)) # 10
backup_control.pop_all() # Pop all elements and trigger the `restore` method for each instance
print(len(backup_control._restorables)) # 0
If any of the restorable fail to restore, the exception is handled in the pop_all()
method by
logging the problem and appending the restorable to BackupController._rollback_failures
list.
Then the restore process continues with the next restorable.