imip-agent

Annotated imiptools/handlers/scheduling/__init__.py

1058:bb1b8e13ef4d
2016-02-09 Paul Boddie Removed explicit confirmation and retraction function usage, instead employing scheduling functions to obtain confirmation, retraction, locking and unlocking functions, with each distinct invocation occurring only once.
paul@938 1
#!/usr/bin/env python
paul@938 2
paul@938 3
"""
paul@938 4
Common scheduling functionality.
paul@938 5
paul@1025 6
Copyright (C) 2015, 2016 Paul Boddie <paul@boddie.org.uk>
paul@938 7
paul@938 8
This program is free software; you can redistribute it and/or modify it under
paul@938 9
the terms of the GNU General Public License as published by the Free Software
paul@938 10
Foundation; either version 3 of the License, or (at your option) any later
paul@938 11
version.
paul@938 12
paul@938 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@938 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@938 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@938 16
details.
paul@938 17
paul@938 18
You should have received a copy of the GNU General Public License along with
paul@938 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@938 20
"""
paul@938 21
paul@1031 22
from imiptools.text import parse_line
paul@1039 23
from imiptools.handlers.scheduling.manifest import confirmation_functions, \
paul@1040 24
                                                   locking_functions, \
paul@1039 25
                                                   retraction_functions, \
paul@1040 26
                                                   scheduling_functions, \
paul@1040 27
                                                   unlocking_functions
paul@1039 28
paul@1039 29
# Function application/invocation.
paul@938 30
paul@1058 31
def apply_scheduling_functions(handler):
paul@1025 32
paul@1025 33
    """
paul@1058 34
    Apply the scheduling functions for the current object of the given
paul@1053 35
    'handler'. This function starts a transaction that should be finalised using
paul@1053 36
    the 'finish_scheduling' function.
paul@1025 37
    """
paul@1025 38
paul@1040 39
    # First, lock the resources to be used.
paul@1040 40
paul@1058 41
    start_scheduling(handler)
paul@1040 42
paul@1053 43
    # Obtain the actual scheduling functions with arguments.
paul@1040 44
paul@1058 45
    schedulers = get_function_calls(handler.get_scheduling_functions(), scheduling_functions)
paul@1040 46
paul@1040 47
    # Then, invoke the scheduling functions.
paul@1031 48
paul@1025 49
    response = "ACCEPTED"
paul@1025 50
paul@1040 51
    for fn, args in schedulers:
paul@1025 52
paul@1025 53
        # NOTE: Should signal an error for incorrectly configured resources.
paul@1025 54
paul@1025 55
        if not fn:
paul@1025 56
            return "DECLINED"
paul@1025 57
paul@1025 58
        # Keep evaluating scheduling functions, stopping only if one
paul@1025 59
        # declines or gives a null response.
paul@1025 60
paul@1025 61
        else:
paul@1031 62
            result = fn(handler, args)
paul@1025 63
paul@1025 64
            # Return a negative result immediately.
paul@1025 65
paul@1025 66
            if not result or result == "DECLINED":
paul@1025 67
                return result
paul@1025 68
paul@1025 69
            # Modify the eventual response from acceptance if a countering
paul@1025 70
            # result is obtained.
paul@1025 71
paul@1025 72
            elif response == "ACCEPTED":
paul@1025 73
                response = result
paul@1025 74
paul@1025 75
    return response
paul@1025 76
paul@1058 77
def confirm_scheduling(handler):
paul@1039 78
paul@1039 79
    """
paul@1058 80
    Confirm scheduling using confirmation functions for the current object of
paul@1058 81
    the given 'handler'. This function continues a transaction that should be
paul@1058 82
    finalised using the 'finish_scheduling' function.
paul@1039 83
    """
paul@1039 84
paul@1058 85
    # Obtain the actual confirmation functions with arguments.
paul@1039 86
paul@1058 87
    functions = get_function_calls(handler.get_scheduling_functions(), confirmation_functions)
paul@1039 88
    apply_functions(functions, handler)
paul@1039 89
paul@1058 90
def retract_scheduling(handler):
paul@1053 91
paul@1053 92
    """
paul@1058 93
    Retract scheduling using retraction functions for the current object of the
paul@1058 94
    given 'handler'. This function is a complete transaction in itself.
paul@1053 95
    """
paul@1053 96
paul@1053 97
    # First, lock the resources to be used.
paul@1053 98
paul@1058 99
    start_scheduling(handler)
paul@1053 100
paul@1058 101
    # Obtain the actual retraction functions with arguments.
paul@1053 102
paul@1058 103
    functions = get_function_calls(handler.get_scheduling_functions(), retraction_functions)
paul@1058 104
    apply_functions(functions, handler)
paul@1053 105
paul@1053 106
    # Finally, unlock the resources.
paul@1053 107
paul@1058 108
    finish_scheduling(handler)
paul@1053 109
paul@1058 110
def start_scheduling(handler):
paul@1053 111
paul@1053 112
    """
paul@1053 113
    Apply locking functions for the given scheduling 'functions' and for the
paul@1053 114
    current object of the given 'handler'.
paul@1053 115
    """
paul@1053 116
paul@1053 117
    # Obtain functions to lock resources.
paul@1053 118
paul@1058 119
    functions = get_function_calls(handler.get_scheduling_functions(), locking_functions)
paul@1058 120
    apply_functions(functions, handler)
paul@1053 121
paul@1058 122
def finish_scheduling(handler):
paul@1040 123
paul@1040 124
    """
paul@1040 125
    Finish scheduling using the given scheduling 'functions' for the current
paul@1040 126
    object of the given 'handler'.
paul@1040 127
    """
paul@1040 128
paul@1040 129
    # Obtain functions to unlock resources.
paul@1040 130
paul@1058 131
    functions = get_function_calls(handler.get_scheduling_functions(), unlocking_functions)
paul@1058 132
    apply_functions(functions, handler)
paul@1039 133
paul@1039 134
def apply_functions(functions, handler):
paul@1039 135
paul@1039 136
    """
paul@1039 137
    Apply the given notification 'functions' for the current object of the given
paul@1058 138
    'handler'. Where functions are provided more than once, they will be called
paul@1058 139
    only once for each distinct set of arguments.
paul@1039 140
    """
paul@1039 141
paul@1058 142
    applied = set()
paul@1058 143
paul@1039 144
    for fn, args in functions:
paul@1039 145
paul@1039 146
        # NOTE: Should signal an error for incorrectly configured resources.
paul@1039 147
paul@1058 148
        if not fn or (fn, args) in applied:
paul@1039 149
            continue
paul@1039 150
paul@1039 151
        fn(handler, args)
paul@1058 152
        applied.add((fn, args))
paul@1039 153
paul@1039 154
# Function lookup.
paul@1039 155
paul@1039 156
def get_function_calls(lines, registry):
paul@1031 157
paul@1031 158
    """
paul@1031 159
    Parse the given 'lines', returning a list of (function, arguments) tuples,
paul@1031 160
    with each function being a genuine function object and with the arguments
paul@1031 161
    being a list of strings.
paul@1031 162
paul@1031 163
    Each of the 'lines' should employ the function name and argument strings
paul@1031 164
    separated by whitespace, with any whitespace inside arguments quoted using
paul@1031 165
    single or double quotes.
paul@1039 166
paul@1039 167
    The given 'registry' indicates the mapping from function names to actual
paul@1039 168
    functions.
paul@1031 169
    """
paul@1031 170
paul@1031 171
    functions = []
paul@1031 172
paul@1031 173
    for line in lines:
paul@1031 174
        parts = parse_line(line)
paul@1058 175
        functions.append((registry.get(parts[0]), tuple(parts[1:])))
paul@1031 176
paul@1031 177
    return functions
paul@1031 178
paul@938 179
# vim: tabstop=4 expandtab shiftwidth=4