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