Passed
Push — master ( c7fbd6...227ddb )
by P.R.
03:02 queued 31s
created

backuppc_clone.command.AutoCommand.AutoCommand.__show_overview_stats()   A

Complexity

Conditions 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nop 1
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
"""
2
BackupPC Clone
3
"""
4
import os
5
6
from cleo import Output
7
8
from backuppc_clone.Config import Config
9
from backuppc_clone.DataLayer import DataLayer
10
from backuppc_clone.command.BaseCommand import BaseCommand
11
from backuppc_clone.helper.AuxiliaryFiles import AuxiliaryFiles
12
from backuppc_clone.helper.BackupClone import BackupClone
13
from backuppc_clone.helper.BackupDelete import BackupDelete
14
from backuppc_clone.helper.BackupInfoScanner import BackupInfoScanner
15
from backuppc_clone.helper.HostDelete import HostDelete
16
from backuppc_clone.helper.PoolSync import PoolSync
17
18
19
class AutoCommand(BaseCommand):
20
    """
21
    Clones the original in automatic mode
22
23
    auto
24
        {clone.cfg : The configuration file of the clone}
25
    """
26
27
    # ------------------------------------------------------------------------------------------------------------------
28
    def __scan_original_backups(self):
29
        """
30
        Scans the original hosts backups.
31
        """
32
        self._io.title('Inventorying Original Backups')
33
34
        helper = BackupInfoScanner(self._io)
35
        helper.scan()
36
        DataLayer.instance.commit()
37
38
    # ------------------------------------------------------------------------------------------------------------------
39
    def __sync_auxiliary_files(self):
40
        """
41
        Synchronises auxiliary files (i.e. files directly under a host directory but not part of a backup).
42
        """
43
        self._io.title('Synchronizing Auxiliary Files')
44
45
        helper = AuxiliaryFiles(self._io)
46
        helper.synchronize()
47
48
    # ------------------------------------------------------------------------------------------------------------------
49
    def __show_overview_stats(self):
50
        """
51
        Shows the number of backups, cloned backups, backups to clone, and number of obsolete cloned backups.
52
        """
53
        stats = DataLayer.instance.overview_get_stats()
54
55
        self._io.writeln(' # backups                : {}'.format(stats['n_backups']))
56
        self._io.writeln(' # cloned backups         : {}'.format(stats['n_cloned_backups']))
57
        self._io.writeln(' # backups still to clone : {}'.format(stats['n_not_cloned_backups']))
58
        self._io.writeln(' # obsolete cloned backups: {}'.format(stats['n_obsolete_cloned_backups']))
59
        self._io.writeln('')
60
61
    # ------------------------------------------------------------------------------------------------------------------
62
    def __remove_obsolete_hosts(self):
63
        """
64
        Removes obsolete hosts.
65
        """
66
        hosts = DataLayer.instance.host_get_obsolete()
67
        if hosts:
68
            self._io.title('Removing Obsolete Hosts')
69
70
            for host in hosts:
71
                self._io.section('Removing host {}'.format(host['hst_name']))
72
73
                helper = HostDelete(self._io)
74
                helper.delete_host(host['hst_name'])
75
76
                DataLayer.instance.commit()
77
78
                self._io.writeln('')
79
80
    # ------------------------------------------------------------------------------------------------------------------
81
    def __remove_obsolete_backups(self):
82
        """
83
        Removes obsolete host backups.
84
        """
85
        backups = DataLayer.instance.backup_get_obsolete()
86 View Code Duplication
        if backups:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
87
            self._io.title('Removing Obsolete Host Backups')
88
89
            for backup in backups:
90
                self._io.section('Removing backup {}/{}'.format(backup['hst_name'], backup['bck_number']))
91
92
                helper = BackupDelete(self._io)
93
                helper.delete_backup(backup['hst_name'], backup['bck_number'])
94
95
                DataLayer.instance.commit()
96
97
                self._io.writeln('')
98
99
    # ------------------------------------------------------------------------------------------------------------------
100
    def __remove_partially_cloned_backups(self):
101
        """
102
        Removes backups that are still marked "in progress" (and hence cloned partially).
103
        """
104
        backups = DataLayer.instance.backup_partially_cloned()
105 View Code Duplication
        if backups:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
106
            self._io.title('Removing Partially Cloned Host Backups')
107
108
            for backup in backups:
109
                self._io.section('Removing backup {}/{}'.format(backup['hst_name'], backup['bck_number']))
110
111
                helper = BackupDelete(self._io)
112
                helper.delete_backup(backup['hst_name'], backup['bck_number'])
113
114
                DataLayer.instance.commit()
115
116
                self._io.writeln('')
117
118
    # ------------------------------------------------------------------------------------------------------------------
119
    def __get_next_clone_target(self):
120
        """
121
        Returns the metadata of the host backup that needs to be cloned.
122
123
        :dict|None:
124
        """
125
        backup = DataLayer.instance.backup_get_next(Config.instance.last_pool_scan)
126
        if not backup:
127
            backup = DataLayer.instance.backup_get_next(-1)
128
129
        return backup
130
131
    # ------------------------------------------------------------------------------------------------------------------
132
    def __resync_pool(self, backup):
133
        """
134
        Resyncs the pool if required for cloning a backup.
135
136
        :param dict backup: The metadata of the backup.
137
        """
138
        if Config.instance.last_pool_scan < backup['bob_end_time']:
139
            self._io.title('Maintaining Clone Pool and Pool Metadata')
140
141
            helper = PoolSync(self._io)
142
            helper.synchronize()
143
144
            DataLayer.instance.commit()
145
146
    # ------------------------------------------------------------------------------------------------------------------
147
    def __clone_backup(self, backup):
148
        """
149
        Clones a backup.
150
151
        :param dict backup: The metadata of the backup.
152
        """
153
        self._io.title('Cloning Backup {}/{}'.format(backup['bob_host'], backup['bob_number']))
154
155
        helper = BackupClone(self._io)
156
        helper.clone_backup(backup['bob_host'], backup['bob_number'])
157
158
        DataLayer.instance.commit()
159
160
    # ------------------------------------------------------------------------------------------------------------------
161
    def __handle_file_not_found(self, backup, error):
162
        """
163
        Handles a FileNotFoundError exception.
164
165
        :param dict backup: The metadata of the backup.
166
        :param FileNotFoundError error: The exception.
167
        """
168
        if self._io.get_verbosity() >= Output.VERBOSITY_VERBOSE:
169
            self._io.warning(str(error))
170
171
        self._io.block('Resynchronization of the pool is required')
172
173
        # The host backup might been partially cloned.
174
        helper = BackupDelete(self._io)
175
        helper.delete_backup(backup['bob_host'], backup['bob_number'])
176
177
        # Force resynchronization of pool.
178
        Config.instance.last_pool_scan = -1
179
180
        # Commit the transaction.
181
        DataLayer.instance.commit()
182
183
    # ------------------------------------------------------------------------------------------------------------------
184
    def _handle_command(self):
185
        """
186
        Executes the command.
187
        """
188
        DataLayer.instance.disconnect()
189
190
        while True:
191
            pid = os.fork()
192
193
            if pid == 0:
194
                DataLayer.instance.connect()
195
196
                self.__remove_partially_cloned_backups()
197
                self.__scan_original_backups()
198
                self.__show_overview_stats()
199
                self.__remove_obsolete_hosts()
200
                self.__remove_obsolete_backups()
201
202
                backup = self.__get_next_clone_target()
203
                if backup is None:
204
                    exit(1)
205
206
                try:
207
                    self.__resync_pool(backup)
208
                    self.__clone_backup(backup)
209
                except FileNotFoundError as error:
210
                    self.__handle_file_not_found(backup, error)
211
212
                exit(0)
213
214
            pid, status = os.wait()
215
            if status != 0:
216
                break
217
218
        self.__sync_auxiliary_files()
219
220
# ----------------------------------------------------------------------------------------------------------------------
221