1
0
mirror of https://github.com/CumulusNetworks/ifupdown2.git synced 2024-05-06 15:54:50 +00:00

251 lines
8.4 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# Copyright (C) 2019 Cumulus Networks, Inc. all rights reserved
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
# https://www.gnu.org/licenses/gpl-2.0-standalone.html
#
# Author:
# Julien Fortin, julien@cumulusnetworks.com
#
# ifupdown2 -- dry_run module
#
#
# __WeakMethodBound and __WeakMethodFree classes as well as the WeakMethod
# function were inspired by the implementation of ActiveState recipes 81253
# weakmethod.
# This code solves an important issue here. You can't have weakrefs on bound
# methods. Here is quote from the recipie:
#
# "Normal weakref.refs to bound methods don't quite work the way one expects,
# because bound methods are first-class objects; weakrefs to bound methods are
# dead-on-arrival unless some other strong reference to the same bound method
# exists."
#
import logging
import inspect
import weakref
class __WeakMethodBound:
""" ActiveState recipes 81253-weakmethod """
def __init__(self, f):
self.f = f.__func__
self.c = weakref.ref(f.__self__)
def __call__(self, *arg, **kwargs):
if not self.c():
raise TypeError("Method called on dead object")
self.f(*(self.c(),) + arg, **kwargs)
class __WeakMethodFree:
""" ActiveState recipes 81253-weakmethod """
def __init__(self, f):
self.f = weakref.ref(f)
def __call__(self, *arg, **kwargs):
if not self.f():
raise TypeError("Function no longer exist")
self.f()(*arg, **kwargs)
def WeakMethod(f):
""" ActiveState recipes 81253-weakmethod """
try:
f.__func__
except AttributeError:
return __WeakMethodFree(f)
return __WeakMethodBound(f)
def _weakref_call_back_delete(reference):
try:
DryRunManager.get_instance().unregister_dry_run_handler_weakref_callback(reference)
except Exception:
pass
class DryRun(object):
"""
Detect dry_run functions and save the associated handler
"""
__DRY_RUN_PREFIX = "DRY-RUN"
def __init__(self):
self.logger = logging.getLogger("ifupdown2.%s" % self.__class__.__name__)
for attr_name in dir(self):
try:
# We need to iterate through the object attribute
# to find dryrun methods
if attr_name.lower().endswith("_dry_run"):
attr_value = getattr(self, attr_name)
# When we find a dryrun attribute we need to make sure
# it is a callable function or method.
if not self.__is_method_or_function(attr_value):
continue
base_attr_name = attr_name[:-8]
base_attr_value = getattr(self, base_attr_name)
# We try infere the base method/function name
# then make sure its a function or method
if not self.__is_method_or_function(base_attr_value):
continue
# now we are pretty sure we have want we want:
# - the base function
# - the associated dry_run code
# we will now register this couple in the DryRunManager
DryRunManager.get_instance().register_dry_run_handler(
weakref.ref(self, _weakref_call_back_delete),
handler_name=base_attr_name,
handler_code_weakref=WeakMethod(base_attr_value),
dry_run_code_weakref=WeakMethod(attr_value)
)
except Exception:
pass
def log_info_ifname_dry_run(self, ifname, string):
self.logger.info("DRY-RUN: %s: %s" % (ifname, string))
def log_info_dry_run(self, string):
self.logger.info("DRY-RUN: %s" % string)
@staticmethod
def __is_method_or_function(obj):
return callable(obj) and (inspect.ismethod(obj) or inspect.isfunction(obj))
class _DryRunEntry(object):
def __init__(self, target_module_weakref, handler_name, handler_code_weakref, dry_run_code_weakref):
self.target_module_weakref = target_module_weakref
self.handler_name = handler_name
self.handler_code_weakref = handler_code_weakref
self.dry_run_code_weakref = dry_run_code_weakref
self.__status = False
def set(self):
target_module_ref = self.target_module_weakref()
if target_module_ref:
if self.dry_run_code_weakref:
target_module_ref.__dict__[self.handler_name] = self.dry_run_code_weakref
self.__status = True
else:
# if the reference is dead we need to unregister it
DryRunManager.get_instance().unregister_dry_run_handler_weakref_callback(self.target_module_weakref)
def unset(self):
target_module_ref = self.target_module_weakref()
if target_module_ref:
if self.handler_code_weakref:
target_module_ref.__dict__[self.handler_name] = self.handler_code_weakref
self.__status = False
else:
# if the reference is dead we need to unregister it
DryRunManager.get_instance().unregister_dry_run_handler_weakref_callback(self.target_module_weakref)
def get_status(self):
return self.__status
class DryRunManager(object):
__instance = None
@staticmethod
def get_instance():
if not DryRunManager.__instance:
DryRunManager.__instance = DryRunManager()
return DryRunManager.__instance
def __init__(self):
if DryRunManager.__instance:
raise RuntimeError("DryRunManager: invalid access. Please use DryRunManager.getInstance()")
else:
DryRunManager.__instance = self
self.__entries = dict()
self.__is_on = False
def register_dry_run_handler(self, module_weakref, handler_name, handler_code_weakref, dry_run_code_weakref):
"""
Register the dry run handler only using weakrefs - we don't want to mess up with garbage collection
:param module_weakref:
:param handler_name:
:param handler_code_weakref:
:param dry_run_code_weakref:
:return:
"""
dry_run_entry = _DryRunEntry(
target_module_weakref=module_weakref,
handler_name=handler_name,
handler_code_weakref=handler_code_weakref,
dry_run_code_weakref=dry_run_code_weakref
)
if self.__is_on:
dry_run_entry.set()
if module_weakref in self.__entries:
self.__entries[module_weakref].append(dry_run_entry)
else:
self.__entries[module_weakref] = [dry_run_entry]
def unregister_dry_run_handler_weakref_callback(self, reference):
"""
If we detect a dead reference, we should remove this reference from our
internal data structure
"""
try:
del self.__entries[reference]
except Exception:
pass
def dry_run_mode_on(self):
"""
Enable the dry run mode
WARNING: not thread-safe
"""
for entries in self.__entries.values():
for entry in entries:
entry.set()
self.__is_on = True
def dry_run_mode_off(self):
"""
Disable the dry run mode
WARNING: not thread-safe
"""
for entries in self.__entries.values():
for entry in entries:
entry.unset()
self.__is_on = False
def dump_entries_stdout(self):
print("== DryRunManager dump ==")
print(" MODULE: HANDLER STATUS")
for entries in self.__entries.values():
for entry in entries:
print(" %s: %s() %s" % (repr(entry.target_module_weakref), entry.handler_name, "ON" if entry.get_status() else "OFF"))
print("========================")
def is_dry_mode_on(self):
return self.__is_on