Bonjour,

Je rencontre un soucis assez complexe à résoudre dans mon implémentation de mon backend.
J'ai créer un daemon qui écoute sur un socket unix et qui attend des commandes. Ces commandes sont défini par des workers, appelé dans une classe workers. La classe workers permet d'encapsuler un système de sauvegarde/restauration en entrée/sortie des différents workers suivant l'état renvoyer par les dits worker. Certain worker hérite d'une classe BckObservable permettant d'appeler une fonction pour sauvegarder certain fichier spécifique qui ne sont pas présent dans la liste des fichiers de base à sauvegarder. J'ai utiliser le design pattern observer pour faire ça. L'instance de mon objet backup doit être le même durant tout le traitement du worker et c'est là que j'ai un soucis. EN effet, pour un de mes workers j'ai besoin de d'avoir accès à mon objet backup non pas dans mon worker mais dans un objet appelé dans le worker, sachant que je ne passe pas mon objet backup à mes workers.

Pour être plus direct, ma classe Workers appelle un de mes classes Configuration (Server), cette classe configuration appelle elle-même un objet SystemConf. C'est cette objet SystemConf qui va lancer la sauvegarde avec les fichiers spécifique, sauf que je n'arrive pas à trouver un moyen d'ajouter un observer sur cette classe depuis ma classe Workers. L'observer ayant besoin d'une instance de mon objet backup en entrée et cet objet backup étant déclarer dans ma classe workers.

Je pense qu'avec du code ça sera plus excplicite.

workers.py (Classe permettant d'appeler mes workers et ayant l'objet self.backup):
Code python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    ==============
    workers module
    ==============
    This module used  to call any worker through the same interface.
    It manage too the backup implementation around the workers
    
    ``classes``
        Workers
"""
from __future__ import absolute_import
from __future__ import unicode_literals
 
# LOCAL IMPORT
from vigie360_backend import LOGGER, DIR_PATH, SETTINGS_DIR_PATH, DJANGO_SETTINGS_MODULE
from vigie360_backend.backup.backup import Backup
from vigie360_backend.import_settings import import_settings
from vigie360_backend.configuration.configuration import Configuration
from vigie360_backend.system_call.system_call import SystemCall
from vigie360_backend.update.update import Update
 
# DJANGO IMPORT
from django.utils.translation import ugettext as _
 
# OTHER IMPORT
import traceback
import inspect
 
import_settings(DIR_PATH, SETTINGS_DIR_PATH, DJANGO_SETTINGS_MODULE)
# locale.setlocale(locale.LC_ALL, str(settings.LANGUAGE_CODE) + str('.utf8'))
 
__author__ = 'tchene'
 
 
class Workers(object):
    """
        Class Workers is a context manager who launch each worker with
        the call_worker() generic function.
        It used to catch and handle exceptions from workers.
 
        ``methods``
            __init__(self, need_save, worker, *data)
            __enter__(self)
            __exit__(self, exc_type, exc_val, exc_tb)
            __factory(worker, *data) (@staticmethod)
            call_worker(self)
    """
 
    def __init__(self, need_save, worker, *data):
        LOGGER.info('Workers object creation')
        LOGGER.debug(worker)
        LOGGER.debug(data)
        self.need_save = need_save
        self.status = '+'
        self.message = ''
        self.worker = self.__factory(worker, *data)
        LOGGER.debug('Worker type : %s' % self.worker)
 
    def __enter__(self):
        """
            This function use to launch backup process according to need_save
            value.
            
            :return: self
        """
        LOGGER.info('Enter workers context manager')
        if self.need_save:
            try:
                self.backup = Backup()
                self.message = self.backup.save()
            except Exception as err:
                self.backup.clean()
                LOGGER.error("__enter__ inspect_trace : %s" % inspect.trace())
                LOGGER.error('An error occurred in __enter__ : %s' % err)
                self.status = '-'
                self.message = _("An error occurred, please contact your administrator")
                raise err
        return self
 
    def __exit__(self, exc_type, exc_val, exc_tb):
        """
            Exceptions are caught in the exit function and used to send back
            error messages and status. Then it executes the restore process if
            needed.
            
            :param exc_type: Exception type
            :param exc_val: Exception value
            :param exc_tb: Exception traceback object
        """
        LOGGER.info('Quit workers context manager')
        if all((exc_type, exc_val, exc_tb)):
            for line in traceback.extract_tb(exc_tb):
                LOGGER.critical('%s' % repr(line))
            LOGGER.debug('EXCEPTION_TYPE : %s' % exc_type)
            LOGGER.debug('EXCEPTION_VAL : %s' % exc_val)
            self.status = '-'
            self.message = _("An error occurred, please contact your administrator")
            try:
                if self.need_save:
                    self.backup.restore()
            except Exception as err:
                LOGGER.error('Worker backup error : %s in %s ' % (err, inspect.trace()[-1][3]))
        if self.need_save:
            self.backup.clean()
 
    @staticmethod
    def __factory(worker, *data):
        """
            This factory return an instance object of a worker class according
            to worker argument sent by front-end connector.
            
            :param worker: The worker name
            :type worker: unicode
            :param data: Data sent bye the front-end
            :type data: undefined
        """
        if worker == 'update': return Update(*data)
        elif worker == 'systemcall': return SystemCall(*data)
        elif worker == 'configuration': return Configuration().factory(*data)
        elif worker == 'deleteconfiguration': return Configuration().factory(deletion=True, *data)
        else: raise Exception('Command sent does not exist')
 
    def call_worker(self):
        """
            Add an observer if the worker class has one. Worker could use
            observer for launch specific action according to an event.
            Then it launch the worker function call() which return status and
            message.
        """
        LOGGER.info('Call_worker')
        if hasattr(self.worker, 'add_bck_observer'):
            self.worker.add_bck_observer(self.backup, 'specific_backup')
        if hasattr(self.worker, 'add_pkg_observer'):
            self.worker.add_pkg_observer(self.backup, 'restore_pip_packets')
            self.worker.add_pkg_observer(self.backup, 'restore_apt_packets')
        self.status, self.message = self.worker.call()

observer.py (Implémentation du design pattern observer):
Code python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    ===============
    observer module
    ===============
 
    Definition of all observable used in the backend.
    Observable are part of the observer pattern.
 
    ``classes``
        _Observable(object)
        BckObservable(_Observable)
"""
from __future__ import unicode_literals
from __future__ import absolute_import
 
# LOCAL IMPORT
from vigie360_backend import LOGGER
 
# OTHER IMPORT
from collections import defaultdict
 
__author__ = 'tchene'
 
class _Observable(object):
    """
        Default observable implementation.
        You must not use this directly and create your own interface.
        
        ``methods``
            __init__(self)
            add_bck_observer(self, obs,  event)
    """
    def __init__(self):
        self.observers = defaultdict(list)  # For each event in a key, got an observers list.
 
    def add_observer(self, obs, event):
        """
            Call this method for add an observer to an event in your class.
            
            :param obs: Object which do some action on some event.
            :type obs: object
            :param event: Event name.
            :type event: unicode, str
        """
        self.observers[event].append(obs)
 
 
class BckObservable(_Observable):
    """
        Backup observable implementation.
        Use it for specific backup.
        You must inherit this class in objects who need a specific backup
        implementation.
        
        ``methods``
            add_bck_observer(self, obs, event)
            specific_backup(self, event, path)
    """
 
    def add_bck_observer(self, obs, event):
        """
            Call this method for add an observer to an event in your class.
            
            :param obs: Object which do some action on some event.
            :type obs: object
            :param event: Event name.
            :type event: unicode, str
        """
        if not hasattr(obs, 'specific_backup'):
            raise ValueError("First argument must be object with specific_save method")
        self.observers[event].append(obs)
 
    def specific_backup(self, event, path):
        """
            Call this method in your inherited class for launch specific backup
            event.
            Call specific_save class
            
            :param event: Event name
            :type event: str, unicode
            :param path: A path or a list of path to backup. They must be
            absolute.
            :type path: str, unicode, list
        """
        LOGGER.debug('Observers : %s' % self.observers)
        for obs in self.observers[event]:
            obs.specific_save(path)
 
 
class PkgObservable(_Observable):
    """
        Package observable implementation.
        Use it for restore packet.
        You must inherit this class in objects who need a packet restore
        implementation.
 
        ``methods``
            add_bck_observer(self, obs, event)
            specific_backup(self, event, path)
    """
 
    def add_pkg_observer(self, obs, event):
        """
            Call this method for add an observer to an event in your class.
 
            :param obs: Object which do some action on some event.
            :type obs: object
            :param event: Event name.
            :type event: unicode, str
        """
        if not hasattr(obs, 'restore_pip_packet') or not hasattr(obs, 'restore_apt_packet'):
            raise ValueError("First argument must be object with restore_pip_packet method or restore_apt_packet "
                             "method")
        self.observers[event].append(obs)
 
    def restore_pip(self, event, packets):
        """
            Call this method in your inherited class for launch pip packet
            restore event.
            Call specific_save class
 
            :param event: Event name
            :type event: str, unicode
            :param packets: List of dictionaries with packet name and version
            :type packets: list
        """
        for obs in self.observers[event]:
            obs.restore_pip_packet(packets)
 
    def restore_apt(self, event, packets):
        """
            Call this method in your inherited class for launch specific backup
            event.
            Call specific_save class
 
            :param event: Event name
            :type event: str, unicode
            :param packets: List of dictionaries with packet name and version
            :type packets: list
        """
        for obs in self.observers[event]:
            obs.restore_apt_packet(packets)

configuration.py (Module contenant l'appel aux classe de configuration ainsi que la classe Server) :
Code python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import absolute_import
 
# LOCAL IMPORT
from vigie360_backend import LOGGER, DIR_PATH, SETTINGS_DIR_PATH, DJANGO_SETTINGS_MODULE
from vigie360_backend.import_settings import import_settings
from vigie360_backend.system_call.system_call import SystemCall
from vigie360_backend.configuration.systemconf import SystemConf
from vigie360_backend.configuration.cfgconf import CfgConf
from vigie360_backend.observer.observer import BckObservable
 
# APP FILES IMPORT
from profiles.forms import *
from supervision.models import SuTimeperiod
from core.models import Setting
 
# LIBS IMPORT
from libs.sync_db_shinken_packs.sync_db import *
from shell_func import hash_postfix
from simpleparser import SimpleParser
from edit_shinken_module import raz_pnp_ip, set_pnp_ip, nagvis_htmlcgi_ip, raz_resource
from config import dhcp_to_static, ModifBase
from servicehandler import change_hostname
from mongodb_custom import raz_mongodb
from settings_parser import settings_write
 
# DJANGO IMPORT
from django.utils.translation import pgettext
from django.utils.translation import ugettext as _
from django.db import connections
from django.core.management import call_command
 
# OTHER IMPORT
from json import dumps
# from distutils.dir_util import copy_tree, remove_tree
# from distutils.file_util import copy_file
# from distutils.errors import DistutilsError, DistutilsFileError
import _mysql
import os
 
__author__ = 'tchene'
 
import_settings(DIR_PATH, SETTINGS_DIR_PATH, DJANGO_SETTINGS_MODULE)
 
join = os.path.join
 
 
# noinspection PyAttributeOutsideInit
class Configuration(object):
    def init(self, data, deletion=False):
        if data:
            LOGGER.debug('DATA CONFIGURATION : %s', data)
            LOGGER.debug('DATA TYPE CONFIGURATION : %s', type(data))
            self.data = data
        self.deletion = deletion
        if hasattr(self, 'data') and not isinstance(self.data, dict):
            raise TypeError('Data must be dict type')
 
    @classmethod
    def factory(cls, cls_type, data=None, deletion=False):
        if cls_type == 'smtp':
            return Smtp(data, deletion)
        if cls_type == 'server':
            return Server(data)
        if cls_type == 'baseconf':
            return Baseconf(data)
        if cls_type == 'raz':
            return Raz(data)
        assert 0, "Bad configuration creation: " + cls_type
 
class Server(Configuration):
    # noinspection PyMissingConstructor
    def __init__(self, data):
        super(Server, self).init(data)
        self.cfg_file = os.path.join(settings.ROOT_DIR, 'server.cfg')
 
    def call(self):
        LOGGER.debug(self.data)
        message_plus = self.add_message_plus()
        cc = CfgConf(self.cfg_file, self.data)
        cc.call()
        sc = SystemConf(self.data['server'])
        sc.call()
        return '+', message_plus
 
    def add_message_plus(self):
        message_plus = list()
        if 'hostname' in self.data['server']:
            self.format_message_plus(message_plus, _('Hostname'), 'hostname')
        if 'ip' in self.data['server']:
            self.format_message_plus(message_plus, _('Ip address'), 'ip')
        if 'domain' in self.data['server']:
            self.format_message_plus(message_plus, _('Domain'), 'domain')
        if 'dns1' in self.data['server']:
            self.format_message_plus(message_plus, _('First DNS'), 'dns1')
        if 'dns2' in self.data['server']:
            self.format_message_plus(message_plus, _('Second DNS'), 'dns2')
        if 'gateway' in self.data['server']:
            self.format_message_plus(message_plus, _('Gateway'), 'gateway')
        if 'subnetmask' in self.data['server']:
            self.format_message_plus(message_plus, _('Subnetmask'), 'subnetmask')
        if 'country' in self.data['server']:
            self.format_message_plus(message_plus, _('Country'), 'country')
        if 'department' in self.data['server']:
            self.format_message_plus(message_plus, _('Department'), 'department')
        if 'city' in self.data['server']:
            self.format_message_plus(message_plus, _('City'), 'city')
        if 'company' in self.data['server']:
            self.format_message_plus(message_plus, _('Company'), 'company')
        return message_plus
 
    def format_message_plus(self, message_plus, desc, key):
        cp = ConfigParser.ConfigParser()
        cp.read(join(settings.ROOT_DIR, 'server.cfg'))
        if cp.has_option('server', key):
            message_plus.append(
                desc + ': ' + cp.get('server', key) + ' -> ' + self.data['server'][key]
                )
        else:
            message_plus.append(
                desc + ': ' + pgettext('male', 'None') + ' -> ' + self.data['server'][key]
                )
        return message_plus

systemconf.py (Contient la classe lançant la sauvegarde spécifique, dans le __init__):
Code python : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    ==============
    systemconf module
    ==============
 
    System configuration module. Import SystemConf Class and use it like in the
    example below :
 
        sc = SystemConf(
        {
            'hostname'  : 'examples',
            'domain'    : 'example.com',
            'ip'        : '192.168.1.1',
            'subnetmask': '255.255.255.0',
            'gateway'   : '192.168.0.5',
            'dns1'      : '8.8.8.8',
            'dns2'      : None
            })
        sc.call()
 
    ``classes``
        SystemConf(Utils, BckObservable)
"""
from __future__ import unicode_literals
from __future__ import absolute_import
 
# LOCAL IMPORT
from vigie360_backend import LOGGER
from vigie360_backend.utils import Utils
from vigie360_backend.observer import BckObservable
from vigie360_backend.system_call.system_call import static_call
 
# OTHER IMPORT
import re
import os
 
 
join = os.path.join
 
__author__ = 'tchene'
 
class SystemConf(Utils, BckObservable):
    """
        System configuration class.
 
        ``methods``
            __init__(self, data)
            _create_static_properties(self)
            _create_dynamic_properties(self, data)
            call(self)
            _get_file_content(file_path) (@staticmethod)
            _write_file_content(file_path, content) (@staticmethod)
            _find_properties(pattern, string, options=None) (@staticmethod)
            _property_replacement(pattern, repl, string) (@staticmethod)
            _add_dns2(self, file_path, value)
            _del_dns2(self, file_path)
            _add_search_domain(self, file_path, value)
    """
 
    def __init__(self, data):
        super(SystemConf, self).__init__()
        self.static_properties = self._create_static_properties()
        if not isinstance(data, dict):
            raise TypeError('Data must be a dictionary')
        self.dynamic_properties = self._create_dynamic_properties(data)
        paths_list = []
        for path_list in [v['files'] for k, v in self.dynamic_properties.iteritems()]:
            paths_list.extend(path_list)
        LOGGER.debug('Conf backup paths : %s' % paths_list)
        self.specific_backup('specific_backup', paths_list)
 
    def _create_static_properties(self):
        """
            Create a static dictionary of dictionary with all properties which
            never change.
            The keys in the first dictionary are properties names, their linked
            with a dictionary which contain all the properties.
            If the property involves multiple files, their set in a list.
            (For compatibility reason, single file are in a list too)
            The add_func, del_func and regex list are in the same order than
            the files list.
            The services list indicate which services will be restart at the end
            of the server configuration.
 
            :return: Static properties dict object
            :rtype: dict
        """
        return {
            "hostname"  : {
                "add_func": [
                                None,
                                None
                            ],
                "del_func": [
                                None,
                                None
                            ],
                "files"   : [
                                '/etc/hosts',
                                '/etc/hostname'
                            ],
                "regex"   : [
                                (r'^127\.0\.1\.1\s*(\S+)\s([-\w]+)\.', re.MULTILINE),
                                (r'^(.+)$', re.MULTILINE)
                            ],
                "services": ['service hostname restart']
                },
 
            "domain"    : {
                "add_func": [
                                None,
                                None,
                                self._add_search_domain,
                            ],
                "del_func": [
                                None,
                                None,
                                None,
                            ],
                "files"   : [
                                '/etc/hosts',
                                '/etc/samba/smb.conf',
                                '/etc/resolvconf/resolv.conf.d/base'
                            ],
                "regex"   : [
                                (r'^127\.0\.1\.1\s*\S+\s[-\w]+\.(.+)$', re.MULTILINE),
                                (r'^\s+workgroup\s=\s(.+)$', re.MULTILINE),
                                (r'^search\s(.+)$', re.MULTILINE)
                            ],
                "services": [
                                'service hostname restart',
                                'service smbd restart'
                            ]
                },
 
            "ip"        : {
                "add_func": [
                                None,
                                None,
                                None,
                            ],
                "del_func": [
                                None,
                                None,
                                None,
                            ],
                "files"   : [
                                '/etc/network/interfaces',
                                '/etc/shinken/modules/ui-pnp.cfg',
                                '/etc/shinken/nagvis/etc/nagvis.ini.php'
                             ],
                "regex"   : [
                                (r'^address\s(.+)$', re.MULTILINE),
                                ('^\s+uri\s+http://(\S+)/pnp4nagios/', re.MULTILINE),
                                ('^htmlcgi\s=\s\"http://(\S+):7767\"', re.MULTILINE)
                             ],
                "services": [
                                'ifdown eth0',
                                'ifup eth0',
                                'service nginx reload',
                                'service shinken restart'
                            ]
                },
 
            "subnetmask": {
                "add_func": [None],
                "del_func": [None],
                "files"   : ['/etc/network/interfaces'],
                "regex"   : [(r'^netmask\s(.+)$', re.MULTILINE)],
                "services": [
                                'ifdown eth0',
                                'ifup eth0'
                             ]
                },
 
            "gateway"   : {
                "add_func": [None],
                "del_func": [None],
                "files"   : ['/etc/network/interfaces'],
                "regex"   : [(r'^gateway\s(.+)$', re.MULTILINE)],
                "services": [
                                'ifdown eth0',
                                'ifup eth0'
                            ]
                },
 
            "dns1"      : {
                "add_func": [None],
                "del_func": [None],
                "files"   : ['/etc/resolvconf/resolv.conf.d/base'],
                "regex"   : [(r'^nameserver\s(.+)', None)],
                "services": ['service resolvconf restart']
                },
 
            "dns2"      : {
                "add_func": [self._add_dns2],
                "del_func": [self._del_dns2],
                "files"   : ['/etc/resolvconf/resolv.conf.d/base'],
                "regex"   : [(r'\nnameserver\s(.+)', None)],
                "services": ['service resolvconf restart']
                },
            }
 
    def _create_dynamic_properties(self, data):
        """
            Merge the static properties with value passed for each property.
            data must be like this :
            {
                'property_name': value
            }
 
            :param data: Dictionary like above
            :type data: dict
            :return: Merged dictionary
            :rtype: dict
        """
        properties = {}
        for k, v in data.iteritems():
            if k in self.static_properties.keys():
                properties[k] = {'value' : v}
        for k in properties:
            properties[k].update(self.static_properties[k])
        LOGGER.debug('Dynamic properties : %s' % properties)
        return properties
 
    def call(self):
        """
            Call function, all process is launched here.
 
            For each key in the dynamic_properties, we launched a regex catch.
            If the regex is existing in the file, we launch the modification,
            else we launch the add method. All property does not have add
            function, because for some properties, it is impossible to not
            exist.
            Finally, if the property is existing and it's value is None,
            we launch the delete function for this property.
            Some properties don't have delete method for the same reason that
            add function does not exist for them, they must be here all time.
 
            (See self._create_static_properties for more details about the regex
            order, the add function and the delete function)
        """
        LOGGER.info('Launch system configuration')
        services = []
        for key, prop in self.dynamic_properties.iteritems():
            if prop['value']:
                for file_path, regex, add_func, in zip(prop['files'], prop['regex'], prop['add_func']):
                    content = self._get_file_content(file_path)
                    grep = self._find_properties(regex[0], content, regex[1])
                    if grep:
                        for group in grep.groups():
                            content = self._property_replacement(group, prop['value'], content)
                        self._write_file_content(file_path, content)
                    else:
                        if add_func:
                            add_func(file_path, prop['value'])
            else:
                for file_path, del_func in zip(prop['files'], prop['del_func']):
                    if del_func:
                        del_func(file_path)
            for service in prop['services']:
                if service not in services:
                    services.append(service)
        static_call(services)
        LOGGER.info('System configuration finished')
 
    @staticmethod
    def _get_file_content(file_path):
        """
            Return the full content of a file
 
            :param file_path: Path to file
            :type file_path: unicode
            :return: File content
            :rtype: str
        """
        with open(file_path, b'r') as fh:
            return fh.read()
 
    @staticmethod
    def _write_file_content(file_path, content):
        """
            Write the content passed to the method in the file define in the
            file_path argument.
 
            :param file_path:
            :type file_path: unicode
            :param content:
            :type content: unicode
        """
        with open(file_path, b'w') as fh:
            # noinspection PyTypeChecker
            fh.write(content)
 
    @staticmethod
    def _find_properties(pattern, string, options=None):
        """
            Find the pattern in the string, if options is given use it with the
            re.search function.
 
            :param pattern: Pattern to match
            :type pattern: unicode
            :param string: The string we look for the pattern
            :type string: unicode
            :param options: Options to pass to re.search
            :type options: re constant like re.MULTILINE
            :return: Return None if the pattern is not find in the string or
            a MatchObject instead
            :rtype: MatchObject or None
        """
        if options:
            return re.search(pattern, string, options)
        else:
            return re.search(pattern, string)
 
    @staticmethod
    def _property_replacement(pattern, repl, string):
        """
            Replace the first matched pattern in string with repl.
 
            :param pattern: Pattern to replace
            :type pattern: unicode
            :param repl: String replacement
            :type repl: unicode
            :param string: The string in which we replace pattern by repl
            :type string
            :return: Return the replaced string
            :rtype: unicode
        """
        return re.sub(pattern, repl, string, 1)
 
    def _add_dns2(self, file_path, value):
        """
            Special function to add the second dns in the resolvconf file.
 
            :param file_path: File path to write
            :type file_path: unicode
            :param value: DNS2 value
            :type value: unicode
        """
        content = self._get_file_content(file_path)
        content = content.strip().split('\n')
        content.insert(1, 'nameserver %s' % value)
        content = '\n'.join(content)
        content = content.strip()
        self._write_file_content(file_path, content)
 
    def _del_dns2(self, file_path):
        """
            Special function to delete the second dns in the resolvconf file.
 
            :param file_path: File path to write
            :type file_path: unicode
        """
        content = self._get_file_content(file_path)
        content = content.strip().split('\n')
        del content[1]
        content = '\n'.join(content)
        content = content.strip()
        self._write_file_content(file_path, content)
 
    def _add_search_domain(self, file_path, value):
        """
            Special function to add the search option in the resolvconf file.
 
            :param file_path: File path to write
            :type file_path: unicode
            :param value: Search value
            :type value: unicode
        """
        content = self._get_file_content(file_path)
        content = content.strip().split('\n')
        content.append('search %s' % value)
        content = '\n'.join(content)
        content = content.strip()
        self._write_file_content(file_path, content)

Désolé pour la classe configuration, j'ai pas eu le temps de faire la docstring.

Si vous avez besoin de plus de précision n'hésitez pas à demander, je conçois très bien que l'explication ne soit pas très claire.

Et merci d'avance à ceux qui se pencheront dessus.