1 #!/usr/bin/env python 2 3 """ 4 Autonomous 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 if one declines or 63 # gives a null response, or if one delegates to another resource. 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 in ("DECLINED", "DELEGATED"): 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 try: 106 107 # Obtain the actual retraction functions with arguments. 108 109 functions = get_function_calls(handler.get_scheduling_functions(), retraction_functions) 110 apply_functions(functions, handler) 111 112 # Finally, unlock the resources. 113 114 finally: 115 finish_scheduling(handler) 116 117 def start_scheduling(handler): 118 119 """ 120 Apply locking functions for the given scheduling 'functions' and for the 121 current object of the given 'handler'. 122 """ 123 124 # Obtain functions to lock resources. 125 126 functions = get_function_calls(handler.get_scheduling_functions(), locking_functions) 127 apply_functions(functions, handler) 128 129 def finish_scheduling(handler): 130 131 """ 132 Finish scheduling using the given scheduling 'functions' for the current 133 object of the given 'handler'. 134 """ 135 136 # Obtain functions to unlock resources. 137 138 functions = get_function_calls(handler.get_scheduling_functions(), unlocking_functions) 139 apply_functions(functions, handler) 140 141 def apply_functions(functions, handler): 142 143 """ 144 Apply the given notification 'functions' for the current object of the given 145 'handler'. Where functions are provided more than once, they will be called 146 only once for each distinct set of arguments. 147 """ 148 149 applied = set() 150 151 for fn, args in functions: 152 153 # NOTE: Should signal an error for incorrectly configured resources. 154 155 if not fn or (fn, args) in applied: 156 continue 157 158 fn(handler, args) 159 applied.add((fn, args)) 160 161 # Function lookup. 162 163 def get_function_calls(lines, registry): 164 165 """ 166 Parse the given 'lines', returning a list of (function, arguments) tuples, 167 with each function being a genuine function object and with the arguments 168 being a list of strings. 169 170 Each of the 'lines' should employ the function name and argument strings 171 separated by whitespace, with any whitespace inside arguments quoted using 172 single or double quotes. 173 174 The given 'registry' indicates the mapping from function names to actual 175 functions. 176 """ 177 178 functions = [] 179 180 for line in lines: 181 parts = parse_line(line) 182 183 # A sequence of functions is provided for each name. 184 185 l = registry.get(parts[0]) 186 if l: 187 for function in l: 188 functions.append((function, tuple(parts[1:]))) 189 190 return functions 191 192 # vim: tabstop=4 expandtab shiftwidth=4