class PDP(object): """ PDP ( Policy Decision Point ) """ def __init__(self, clients=None): """ Constructor. examples: >>> pdp = PDP( None ) >>> pdp1 = PDP( {} ) >>> pdp2 = PDP( { 'Client1' : Client1Object } ) :Parameters: **clients** - [ None, `dict` ] dictionary with Clients to be used in the Commands. If None, the Commands will create their own clients. """ # decision parameters used to match policies and actions self.decisionParams = None # Helpers to discover policies and RSS metadata in CS self.pCaller = PolicyCaller(clients) # RSS State Machine, used to calculate most penalizing state while merging them self.rssMachine = RSSMachine('Unknown') self.log = gLogger.getSubLogger('PDP') def setup(self, decisionParams=None): """ method that sanitizes the decisionParams and ensures that at least it has the keys in `standardParamsDict`. This will be relevant while doing the matching with the RSS Policies configuration in the CS. There is one key-value pair, `active` which is added on this method. This allows policies to be de-activated from the CS, changing their active matchParam to something else than `Active`. examples: >>> pdp.setup( None ) >>> self.decisionParams { 'element' : None, 'name' : None, ... } >>> pdp.setup( { 'element' : 'AnElement' } ) >>> self.decisionParams { 'element' : 'AnElement', 'name' : None, ... } >>> pdp.setup( { 'NonStandardKey' : 'Something' } ) >>> self.decisionParams { 'NonStandardKey' : 'Something', 'element' : None,... } :Parameters: **decisionParams** - [ None, `dict` ] dictionary with the parameters to be matched with the RSS Policies configuration in the CS. """ standardParamsDict = { 'element': None, 'name': None, 'elementType': None, 'statusType': None, 'status': None, 'reason': None, 'tokenOwner': None, # Last parameter allows policies to be de-activated 'active': 'Active' } if decisionParams is not None: standardParamsDict.update(decisionParams) if standardParamsDict['element'] is not None: self.log = gLogger.getSubLogger('PDP/%s' % standardParamsDict['element']) if standardParamsDict['name'] is not None: self.log = gLogger.getSubLogger( 'PDP/%s/%s' % (standardParamsDict['element'], standardParamsDict['name'])) self.log.verbose("Setup - statusType: %s, status: %s" % (standardParamsDict['statusType'], standardParamsDict['status'])) self.decisionParams = standardParamsDict def takeDecision(self): """ main PDP method which does all the work. If firstly finds all the policies defined in the CS that match <self.decisionParams> and runs them. Once it has all the singlePolicyResults, it combines them. Next step is action discovery: using a similar approach to the one used to discover the policies, but also taking into account the single policy results and their combined result, finds the actions to be triggered and returns. examples: >>> pdp.takeDecision()['Value'].keys() ['singlePolicyResults', 'policyCombinedResult', 'decisionParams'] >>> pdp.takeDecision()['Value']['singlePolicyResults'] [ { 'Status' : 'Active', 'Reason' : 'blah', 'Policy' : { 'name' : 'AlwaysActiveForResource', 'type' : 'AlwaysActive', 'module' : 'AlwaysActivePolicy', 'description' : 'This is the AlwaysActive policy' 'command' : None, 'args' : {} } }, ... ] >>> pdp.takeDecision()['Value']['policyCombinedResult'] { 'Status' : 'Active', 'Reason' : 'blah ###', 'PolicyAction' : [ ( 'policyActionName1', 'policyActionType1' ), ... ] } :return: S_OK( { 'singlePolicyResults' : `list`, 'policyCombinedResult' : `dict`, 'decisionParams' : `dict` } ) / S_ERROR """ if self.decisionParams is None: return S_OK({ 'singlePolicyResults': [], 'policyCombinedResult': {}, 'decisionParams': self.decisionParams }) self.log.verbose("Taking decision") # Policies.................................................................. # Get policies that match self.decisionParams policiesThatApply = getPoliciesThatApply(self.decisionParams) if not policiesThatApply['OK']: return policiesThatApply policiesThatApply = policiesThatApply['Value'] self.log.verbose("Policies that apply: %s" % ', '.join([po['name'] for po in policiesThatApply])) # Evaluate policies singlePolicyResults = self._runPolicies(policiesThatApply) if not singlePolicyResults['OK']: return singlePolicyResults singlePolicyResults = singlePolicyResults['Value'] self.log.verbose("Single policy results: %s" % singlePolicyResults) # Combine policies and get most penalizing status ( see RSSMachine ) policyCombinedResults = self._combineSinglePolicyResults( singlePolicyResults) if not policyCombinedResults['OK']: return policyCombinedResults policyCombinedResults = policyCombinedResults['Value'] self.log.verbose("Combined policy result: %s" % policyCombinedResults) # Actions................................................................... policyActionsThatApply = getPolicyActionsThatApply( self.decisionParams, singlePolicyResults, policyCombinedResults) if not policyActionsThatApply['OK']: return policyActionsThatApply policyActionsThatApply = policyActionsThatApply['Value'] self.log.verbose("Policy actions that apply: %s" % ','.join(pata[0] for pata in policyActionsThatApply)) policyCombinedResults['PolicyAction'] = policyActionsThatApply return S_OK({ 'singlePolicyResults': singlePolicyResults, 'policyCombinedResult': policyCombinedResults, 'decisionParams': self.decisionParams }) def _runPolicies(self, policies): """ Given a list of policy dictionaries, loads them making use of the PolicyCaller and evaluates them. This method requires to have run setup previously. examples: >>> pdp._runPolicies([])['Value'] [] >>> policyDict = { 'name' : 'AlwaysActiveResource', 'type' : 'AlwaysActive', 'args' : None, 'description' : 'This is the AlwaysActive policy', 'module' : 'AlwaysActivePolicy', 'command' : None } >>> pdp._runPolicies([ policyDict, ... ] )['Value'] [ { 'Status' : 'Active', 'Reason' : 'blah', 'Policy' : policyDict }, ... ] :Parameters: **policies** - `list( dict )` list of dictionaries containing the policies selected to be run. Check the examples to get an idea of how the policy dictionaries look like. :return: S_OK() / S_ERROR """ policyInvocationResults = [] # Gets all valid status for RSS to avoid misconfigured policies returning statuses # that RSS does not understand. validStatus = self.rssMachine.getStates() for policyDict in policies: # Load and evaluate policy described in <policyDict> for element described # in <self.decisionParams> policyInvocationResult = self.pCaller.policyInvocation( self.decisionParams, policyDict) if not policyInvocationResult['OK']: # We should never enter this line ! Just in case there are policies # missconfigured ! _msg = 'runPolicies no OK: %s' % policyInvocationResult self.log.error(_msg) return S_ERROR(_msg) policyInvocationResult = policyInvocationResult['Value'] # Sanity Checks ( they should never happen ! ) if not 'Status' in policyInvocationResult: _msg = 'runPolicies (no Status): %s' % policyInvocationResult self.log.error(_msg) return S_ERROR(_msg) if not policyInvocationResult['Status'] in validStatus: _msg = 'runPolicies ( not valid status ) %s' % policyInvocationResult[ 'Status'] self.log.error(_msg) return S_ERROR(_msg) if not 'Reason' in policyInvocationResult: _msg = 'runPolicies (no Reason): %s' % policyInvocationResult self.log.error(_msg) return S_ERROR(_msg) policyInvocationResults.append(policyInvocationResult) return S_OK(policyInvocationResults) def _combineSinglePolicyResults(self, singlePolicyRes): """ method that merges all the policies results into a combined one, which will be the most penalizing status and the reasons of the single policy results that returned the same penalizing status. All the rest, are ignored. If there are no single policy results, it is returned `Unknown` state. While combining policies, the ones containing the option `doNotCombine` are ignored. examples: >>> pdp._combineSingePolicyResults( [] )['Value'] { 'Status' : 'Unknown', 'Reason' : 'No policy ..' } >>> pdp._combineSingePolicyResults( [ { 'Status' : 'Active', 'Reason' : 'blah', 'Policy' : policyDict } ] ) { 'Status' : 'Active', 'Reason' : 'blah' } >>> pdp._combineSingePolicyResults( [ { 'Status' : 'Active', 'Reason' : 'blah', 'Policy' : policyDict }, { 'Status' : 'Banned', 'Reason' : 'blah 2', 'Policy' : policyDict2 } ] ) { 'Status' : 'Banned', 'Reason' : 'blah 2' } >>> pdp._combineSingePolicyResults( [ { 'Status' : 'Active', 'Reason' : 'blah', 'Policy' : policyDict }, { 'Status' : 'Active', 'Reason' : 'blah 2', 'Policy' : policyDict2 } ] ) { 'Status' : 'Banned', 'Reason' : 'blah ### blah 2' } :Parameters: **singlePolicyRes** - `list( dict )` list with every single policy result to be combined ( see _runPolicy for more details ) :return: S_OK( dict( Status, Reason ) | S_ERROR """ # Dictionary to be returned policyCombined = { 'Status': 'Unknown', # default, it should be overridden by the policies, if they exist 'Reason': '' } # If there are no policyResults, we return Unknown if not singlePolicyRes: policyCombined[ 'Reason'] = 'No policy applies to %(element)s, %(name)s, %(elementType)s' % self.decisionParams self.log.warn(policyCombined['Reason']) return S_OK(policyCombined) # We set the rssMachine on the current state ( ensures it is a valid one ) # FIXME: probably this check can be done at takeDecision machineStatus = self.rssMachine.setState(self.decisionParams['status']) if not machineStatus['OK']: return machineStatus # Discard all single policy results which belongs to policies that have set # the option `doNotCombine` in the CS policiesToCombine = self._findPoliciesToCombine(singlePolicyRes) # Sort policy results using ther statuses by most restrictive ( lower level first ) self.rssMachine.orderPolicyResults(policiesToCombine) # As they have been sorted by most restrictive status, the first one is going # to be our candidate new state. Let's ask the RSSMachine if it allows us to # make such transition. candidateState = policiesToCombine[0]['Status'] nextState = self.rssMachine.getNextState(candidateState) if not nextState['OK']: return nextState nextState = nextState['Value'] # If the RssMachine does not accept the candidate, return forcing message if candidateState != nextState: policyCombined['Status'] = nextState policyCombined['Reason'] = 'RssMachine forced status %s to %s' % ( candidateState, nextState) return S_OK(policyCombined) # If the RssMachine accepts the candidate, just concatenate the reasons for policyRes in policiesToCombine: if policyRes['Status'] == nextState: policyCombined['Reason'] += '%s ###' % policyRes['Reason'] policyCombined['Status'] = nextState return S_OK(policyCombined) def _findPoliciesToCombine(self, singlePolicyRes): """ method that iterates over the single policy results and checks the CS configuration of the policies looking for the option 'doNotCombine'. If it is present, that single policy result is discarded. :Parameters: **singlePolicyRes** - `list( dict )` list with every single policy result to be combined ( see _runPolicy for more details ) :return: `list( dict )` """ # Get policies configuration from the CS. We want to exclude the policies that # have set the option `doNotCombine` from this process. policiesConfiguration = RssConfiguration.getPolicies() if not policiesConfiguration['OK']: return policiesConfiguration policiesConfiguration = policiesConfiguration['Value'] # Function that let's us know if we should combine the result of a single policy # or not. def combinePolicy(policyResult): # Extract policy name from the dictionary returned by PolicyCaller policyName = policyResult['Policy']['name'] try: # If doNotCombineResult is defined, the policy is not taken into account # to create the combined result. However, the single policy result remains _ = policiesConfiguration[policyName]['doNotCombineResult'] return False except KeyError: return True # Make a list of policies of which we want to merge their results return [ policyResult for policyResult in singlePolicyRes if combinePolicy(policyResult) ]
class PDP: """ The PDP (Policy Decision Point) module is used to: 1. Decides which policies have to be applied. 2. Invokes an evaluation of the policies, and returns the result (to a PEP) """ def __init__(self, clients): ''' Constructor. Defines members that will be used later on. ''' self.pCaller = PolicyCaller(clients=clients) self.iGetter = InfoGetter() self.decissionParams = {} self.rssMachine = RSSMachine('Unknown') def setup(self, decissionParams=None): standardParamsDict = { 'element': None, 'name': None, 'elementType': None, 'statusType': None, 'status': None, 'reason': None, 'tokenOwner': None, # Last parameter allows policies to be deactivated 'active': 'Active' } if decissionParams is not None: standardParamsDict.update(decissionParams) self.decissionParams = standardParamsDict ################################################################################ def takeDecision( self): #, policyIn = None, argsIn = None, knownInfo = None ): """ PDP MAIN FUNCTION decides policies that have to be applied, based on __granularity, __name, __status, __formerStatus __reason If more than one policy is evaluated, results are combined. Logic for combination: a conservative approach is followed (i.e. if a site should be banned for at least one policy, that's what is returned) returns: { 'PolicyType': a policyType (in a string), 'Action': True|False, 'Status': 'Active'|'Probing'|'Banned', 'Reason': a reason #'EndDate: datetime.datetime (in a string)} """ policiesThatApply = self.iGetter.getPoliciesThatApply( self.decissionParams) if not policiesThatApply['OK']: return policiesThatApply policiesThatApply = policiesThatApply['Value'] singlePolicyResults = self._runPolicies(policiesThatApply) if not singlePolicyResults['OK']: return singlePolicyResults singlePolicyResults = singlePolicyResults['Value'] policyCombinedResults = self._combineSinglePolicyResults( singlePolicyResults) if not policyCombinedResults['OK']: return policyCombinedResults policyCombinedResults = policyCombinedResults['Value'] policyActionsThatApply = self.iGetter.getPolicyActionsThatApply( self.decissionParams, singlePolicyResults, policyCombinedResults) if not policyActionsThatApply['OK']: return policyActionsThatApply policyActionsThatApply = policyActionsThatApply['Value'] policyCombinedResults['PolicyAction'] = policyActionsThatApply return S_OK({ 'singlePolicyResults': singlePolicyResults, 'policyCombinedResult': policyCombinedResults, 'decissionParams': self.decissionParams }) ################################################################################ def _runPolicies(self, policies, decissionParams=None): if decissionParams is None: decissionParams = self.decissionParams validStatus = RssConfiguration.getValidStatus() if not validStatus['OK']: return validStatus validStatus = validStatus['Value'] policyInvocationResults = [] for policyDict in policies: policyInvocationResult = self.pCaller.policyInvocation( decissionParams, policyDict) if not policyInvocationResult['OK']: # We should never enter this line ! Just in case there are policies # missconfigured ! _msg = 'runPolicies no OK: %s' % policyInvocationResult gLogger.error(_msg) return S_ERROR(_msg) policyInvocationResult = policyInvocationResult['Value'] if not 'Status' in policyInvocationResult: _msg = 'runPolicies (no Status): %s' % policyInvocationResult gLogger.error(_msg) return S_ERROR(_msg) if not policyInvocationResult['Status'] in validStatus: _msg = 'runPolicies ( not valid status ) %s' % policyInvocationResult[ 'Status'] gLogger.error(_msg) return S_ERROR(_msg) if not 'Reason' in policyInvocationResult: _msg = 'runPolicies (no Reason): %s' % policyInvocationResult gLogger.error(_msg) return S_ERROR(_msg) policyInvocationResults.append(policyInvocationResult) return S_OK(policyInvocationResults) ################################################################################ def _combineSinglePolicyResults(self, singlePolicyRes): ''' singlePolicyRes = [ { 'State' : X, 'Reason' : Y, ... }, ... ] If there are no policyResults, returns Unknown as there are no policies to apply. Order elements in list by state, being the lowest the most restrictive one in the hierarchy. ''' # Dictionary to be returned policyCombined = {'Status': None, 'Reason': ''} # If there are no policyResults, we return Unknown if not singlePolicyRes: _msgTuple = (self.decissionParams['element'], self.decissionParams['name'], self.decissionParams['elementType']) policyCombined['Status'] = 'Unknown' policyCombined[ 'Reason'] = 'No policy applies to %s, %s, %s' % _msgTuple return S_OK(policyCombined) # We set the rssMachine on the current state machineStatus = self.rssMachine.setState( self.decissionParams['status']) if not machineStatus['OK']: return machineStatus # Order statuses by most restrictive ( lower level first ) self.rssMachine.orderPolicyResults(singlePolicyRes) #policyResults = self.rssMachine.orderPolicyResults( singlePolicyRes ) # Get according to the RssMachine the next state, given a candidate candidateState = singlePolicyRes[0]['Status'] nextState = self.rssMachine.getNextState(candidateState) if not nextState['OK']: return nextState nextState = nextState['Value'] # If the RssMachine does not accept the candidate, return forcing message if candidateState != nextState: policyCombined['Status'] = nextState policyCombined['Reason'] = 'RssMachine forced status %s to %s' % ( candidateState, nextState) return S_OK(policyCombined) # If the RssMachine accepts the candidate, just concatenate the reasons for policyRes in singlePolicyRes: if policyRes['Status'] == nextState: policyCombined['Reason'] += '%s ###' % policyRes['Reason'] policyCombined['Status'] = nextState return S_OK(policyCombined) ################################################################################ # def __useOldPolicyRes( self, name, policyName ): # ''' # Use the RSS Service to get an old policy result. # If such result is older than 2 hours, it returns {'Status':'Unknown'} # ''' # res = self.clients[ 'ResourceManagementClient' ].getPolicyResult( name = name, policyName = policyName ) # # if not res[ 'OK' ]: # return { 'Status' : 'Unknown' } # # res = res[ 'Value' ] # # if res == []: # return { 'Status' : 'Unknown' } # # res = res[ 0 ] # # oldStatus = res[ 5 ] # oldReason = res[ 6 ] # lastCheckTime = res[ 8 ] # # if ( lastCheckTime + datetime.timedelta(hours = 2) ) < datetime.datetime.utcnow(): # return { 'Status' : 'Unknown' } # # result = {} # # result[ 'Status' ] = oldStatus # result[ 'Reason' ] = oldReason # result[ 'OLD' ] = True # result[ 'PolicyName' ] = policyName # # return result ################################################################################ #EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF
class PDP: """ The PDP (Policy Decision Point) module is used to: 1. Decides which policies have to be applied. 2. Invokes an evaluation of the policies, and returns the result (to a PEP) """ def __init__(self, clients): """ Constructor. Defines members that will be used later on. """ self.pCaller = PolicyCaller(clients=clients) self.iGetter = InfoGetter() self.decissionParams = {} self.rssMachine = RSSMachine("Unknown") def setup(self, decissionParams=None): standardParamsDict = { "element": None, "name": None, "elementType": None, "statusType": None, "status": None, "reason": None, "tokenOwner": None, # Last parameter allows policies to be deactivated "active": "Active", } if decissionParams is not None: standardParamsDict.update(decissionParams) self.decissionParams = standardParamsDict ################################################################################ def takeDecision(self): # , policyIn = None, argsIn = None, knownInfo = None ): """ PDP MAIN FUNCTION decides policies that have to be applied, based on __granularity, __name, __status, __formerStatus __reason If more than one policy is evaluated, results are combined. Logic for combination: a conservative approach is followed (i.e. if a site should be banned for at least one policy, that's what is returned) returns: { 'PolicyType': a policyType (in a string), 'Action': True|False, 'Status': 'Active'|'Probing'|'Banned', 'Reason': a reason #'EndDate: datetime.datetime (in a string)} """ policiesThatApply = self.iGetter.getPoliciesThatApply(self.decissionParams) if not policiesThatApply["OK"]: return policiesThatApply policiesThatApply = policiesThatApply["Value"] singlePolicyResults = self._runPolicies(policiesThatApply) if not singlePolicyResults["OK"]: return singlePolicyResults singlePolicyResults = singlePolicyResults["Value"] policyCombinedResults = self._combineSinglePolicyResults(singlePolicyResults) if not policyCombinedResults["OK"]: return policyCombinedResults policyCombinedResults = policyCombinedResults["Value"] policyActionsThatApply = self.iGetter.getPolicyActionsThatApply( self.decissionParams, singlePolicyResults, policyCombinedResults ) if not policyActionsThatApply["OK"]: return policyActionsThatApply policyActionsThatApply = policyActionsThatApply["Value"] policyCombinedResults["PolicyAction"] = policyActionsThatApply return S_OK( { "singlePolicyResults": singlePolicyResults, "policyCombinedResult": policyCombinedResults, "decissionParams": self.decissionParams, } ) ################################################################################ def _runPolicies(self, policies, decissionParams=None): if decissionParams is None: decissionParams = self.decissionParams validStatus = RssConfiguration.getValidStatus() if not validStatus["OK"]: return validStatus validStatus = validStatus["Value"] policyInvocationResults = [] for policyDict in policies: policyInvocationResult = self.pCaller.policyInvocation(decissionParams, policyDict) if not policyInvocationResult["OK"]: # We should never enter this line ! Just in case there are policies # missconfigured ! _msg = "runPolicies no OK: %s" % policyInvocationResult gLogger.error(_msg) return S_ERROR(_msg) policyInvocationResult = policyInvocationResult["Value"] if not "Status" in policyInvocationResult: _msg = "runPolicies (no Status): %s" % policyInvocationResult gLogger.error(_msg) return S_ERROR(_msg) if not policyInvocationResult["Status"] in validStatus: _msg = "runPolicies ( not valid status ) %s" % policyInvocationResult["Status"] gLogger.error(_msg) return S_ERROR(_msg) if not "Reason" in policyInvocationResult: _msg = "runPolicies (no Reason): %s" % policyInvocationResult gLogger.error(_msg) return S_ERROR(_msg) policyInvocationResults.append(policyInvocationResult) return S_OK(policyInvocationResults) ################################################################################ def _combineSinglePolicyResults(self, singlePolicyRes): """ singlePolicyRes = [ { 'State' : X, 'Reason' : Y, ... }, ... ] If there are no policyResults, returns Unknown as there are no policies to apply. Order elements in list by state, being the lowest the most restrictive one in the hierarchy. """ # Dictionary to be returned policyCombined = {"Status": None, "Reason": ""} # If there are no policyResults, we return Unknown if not singlePolicyRes: _msgTuple = ( self.decissionParams["element"], self.decissionParams["name"], self.decissionParams["elementType"], ) policyCombined["Status"] = "Unknown" policyCombined["Reason"] = "No policy applies to %s, %s, %s" % _msgTuple return S_OK(policyCombined) # We set the rssMachine on the current state machineStatus = self.rssMachine.setState(self.decissionParams["status"]) if not machineStatus["OK"]: return machineStatus # Order statuses by most restrictive ( lower level first ) self.rssMachine.orderPolicyResults(singlePolicyRes) # policyResults = self.rssMachine.orderPolicyResults( singlePolicyRes ) # Get according to the RssMachine the next state, given a candidate candidateState = singlePolicyRes[0]["Status"] nextState = self.rssMachine.getNextState(candidateState) if not nextState["OK"]: return nextState nextState = nextState["Value"] # If the RssMachine does not accept the candidate, return forcing message if candidateState != nextState: policyCombined["Status"] = nextState policyCombined["Reason"] = "RssMachine forced status %s to %s" % (candidateState, nextState) return S_OK(policyCombined) # If the RssMachine accepts the candidate, just concatenate the reasons for policyRes in singlePolicyRes: if policyRes["Status"] == nextState: policyCombined["Reason"] += "%s ###" % policyRes["Reason"] policyCombined["Status"] = nextState return S_OK(policyCombined)
class PDP( object ): """ PDP ( Policy Decision Point ) """ def __init__( self, clients = None ): """ Constructor. examples: >>> pdp = PDP( None ) >>> pdp1 = PDP( {} ) >>> pdp2 = PDP( { 'Client1' : Client1Object } ) :Parameters: **clients** - [ None, `dict` ] dictionary with Clients to be used in the Commands. If None, the Commands will create their own clients. """ # decision parameters used to match policies and actions self.decisionParams = None # Helpers to discover policies and RSS metadata in CS self.pCaller = PolicyCaller( clients ) # RSS State Machine, used to calculate most penalizing state while merging them self.rssMachine = RSSMachine( 'Unknown' ) self.log = gLogger.getSubLogger( 'PDP' ) def setup( self, decisionParams = None ): """ method that sanitizes the decisionParams and ensures that at least it has the keys in `standardParamsDict`. This will be relevant while doing the matching with the RSS Policies configuration in the CS. There is one key-value pair, `active` which is added on this method. This allows policies to be de-activated from the CS, changing their active matchParam to something else than `Active`. examples: >>> pdp.setup( None ) >>> self.decisionParams { 'element' : None, 'name' : None, ... } >>> pdp.setup( { 'element' : 'AnElement' } ) >>> self.decisionParams { 'element' : 'AnElement', 'name' : None, ... } >>> pdp.setup( { 'NonStandardKey' : 'Something' } ) >>> self.decisionParams { 'NonStandardKey' : 'Something', 'element' : None,... } :Parameters: **decisionParams** - [ None, `dict` ] dictionary with the parameters to be matched with the RSS Policies configuration in the CS. """ standardParamsDict = {'element' : None, 'name' : None, 'elementType' : None, 'statusType' : None, 'status' : None, 'reason' : None, 'tokenOwner' : None, # Last parameter allows policies to be de-activated 'active' : 'Active'} if decisionParams is not None: standardParamsDict.update( decisionParams ) if standardParamsDict['element'] is not None: self.log = gLogger.getSubLogger( 'PDP/%s' % standardParamsDict['element'] ) if standardParamsDict['name'] is not None: self.log = gLogger.getSubLogger( 'PDP/%s/%s' % ( standardParamsDict['element'], standardParamsDict['name'] ) ) self.log.verbose( "Setup - statusType: %s, status: %s" % ( standardParamsDict['statusType'], standardParamsDict['status'] ) ) self.decisionParams = standardParamsDict def takeDecision( self ): """ main PDP method which does all the work. If firstly finds all the policies defined in the CS that match <self.decisionParams> and runs them. Once it has all the singlePolicyResults, it combines them. Next step is action discovery: using a similar approach to the one used to discover the policies, but also taking into account the single policy results and their combined result, finds the actions to be triggered and returns. examples: >>> pdp.takeDecision()['Value'].keys() ['singlePolicyResults', 'policyCombinedResult', 'decisionParams'] >>> pdp.takeDecision()['Value']['singlePolicyResults'] [ { 'Status' : 'Active', 'Reason' : 'blah', 'Policy' : { 'name' : 'AlwaysActiveForResource', 'type' : 'AlwaysActive', 'module' : 'AlwaysActivePolicy', 'description' : 'This is the AlwaysActive policy' 'command' : None, 'args' : {} } }, ... ] >>> pdp.takeDecision()['Value']['policyCombinedResult'] { 'Status' : 'Active', 'Reason' : 'blah ###', 'PolicyAction' : [ ( 'policyActionName1', 'policyActionType1' ), ... ] } :return: S_OK( { 'singlePolicyResults' : `list`, 'policyCombinedResult' : `dict`, 'decisionParams' : `dict` } ) / S_ERROR """ if self.decisionParams is None: return S_OK( {'singlePolicyResults' : [], 'policyCombinedResult' : {}, 'decisionParams' : self.decisionParams} ) self.log.verbose( "Taking decision" ) # Policies.................................................................. # Get policies that match self.decisionParams policiesThatApply = getPoliciesThatApply( self.decisionParams ) if not policiesThatApply['OK']: return policiesThatApply policiesThatApply = policiesThatApply['Value'] self.log.verbose( "Policies that apply: %s" % ', '.join( [po['name'] for po in policiesThatApply] ) ) # Evaluate policies singlePolicyResults = self._runPolicies( policiesThatApply ) if not singlePolicyResults['OK']: return singlePolicyResults singlePolicyResults = singlePolicyResults['Value'] self.log.verbose( "Single policy results: %s" % singlePolicyResults ) # Combine policies and get most penalizing status ( see RSSMachine ) policyCombinedResults = self._combineSinglePolicyResults( singlePolicyResults ) if not policyCombinedResults['OK']: return policyCombinedResults policyCombinedResults = policyCombinedResults['Value'] self.log.verbose( "Combined policy result: %s" % policyCombinedResults ) # Actions................................................................... policyActionsThatApply = getPolicyActionsThatApply( self.decisionParams, singlePolicyResults, policyCombinedResults ) if not policyActionsThatApply['OK']: return policyActionsThatApply policyActionsThatApply = policyActionsThatApply['Value'] self.log.verbose( "Policy actions that apply: %s" % ','.join( pata[0] for pata in policyActionsThatApply ) ) policyCombinedResults['PolicyAction'] = policyActionsThatApply return S_OK( {'singlePolicyResults' : singlePolicyResults, 'policyCombinedResult' : policyCombinedResults, 'decisionParams' : self.decisionParams} ) def _runPolicies( self, policies ): """ Given a list of policy dictionaries, loads them making use of the PolicyCaller and evaluates them. This method requires to have run setup previously. examples: >>> pdp._runPolicies([])['Value'] [] >>> policyDict = { 'name' : 'AlwaysActiveResource', 'type' : 'AlwaysActive', 'args' : None, 'description' : 'This is the AlwaysActive policy', 'module' : 'AlwaysActivePolicy', 'command' : None } >>> pdp._runPolicies([ policyDict, ... ] )['Value'] [ { 'Status' : 'Active', 'Reason' : 'blah', 'Policy' : policyDict }, ... ] :Parameters: **policies** - `list( dict )` list of dictionaries containing the policies selected to be run. Check the examples to get an idea of how the policy dictionaries look like. :return: S_OK() / S_ERROR """ policyInvocationResults = [] # Gets all valid status for RSS to avoid misconfigured policies returning statuses # that RSS does not understand. validStatus = self.rssMachine.getStates() for policyDict in policies: # Load and evaluate policy described in <policyDict> for element described # in <self.decisionParams> policyInvocationResult = self.pCaller.policyInvocation( self.decisionParams, policyDict ) if not policyInvocationResult['OK']: # We should never enter this line ! Just in case there are policies # missconfigured ! _msg = 'runPolicies no OK: %s' % policyInvocationResult self.log.error( _msg ) return S_ERROR( _msg ) policyInvocationResult = policyInvocationResult['Value'] # Sanity Checks ( they should never happen ! ) if not 'Status' in policyInvocationResult: _msg = 'runPolicies (no Status): %s' % policyInvocationResult self.log.error( _msg ) return S_ERROR( _msg ) if not policyInvocationResult['Status'] in validStatus: _msg = 'runPolicies ( not valid status ) %s' % policyInvocationResult['Status'] self.log.error( _msg ) return S_ERROR( _msg ) if not 'Reason' in policyInvocationResult: _msg = 'runPolicies (no Reason): %s' % policyInvocationResult self.log.error( _msg ) return S_ERROR( _msg ) policyInvocationResults.append( policyInvocationResult ) return S_OK( policyInvocationResults ) def _combineSinglePolicyResults( self, singlePolicyRes ): """ method that merges all the policies results into a combined one, which will be the most penalizing status and the reasons of the single policy results that returned the same penalizing status. All the rest, are ignored. If there are no single policy results, it is returned `Unknown` state. While combining policies, the ones containing the option `doNotCombine` are ignored. examples: >>> pdp._combineSingePolicyResults( [] )['Value'] { 'Status' : 'Unknown', 'Reason' : 'No policy ..' } >>> pdp._combineSingePolicyResults( [ { 'Status' : 'Active', 'Reason' : 'blah', 'Policy' : policyDict } ] ) { 'Status' : 'Active', 'Reason' : 'blah' } >>> pdp._combineSingePolicyResults( [ { 'Status' : 'Active', 'Reason' : 'blah', 'Policy' : policyDict }, { 'Status' : 'Banned', 'Reason' : 'blah 2', 'Policy' : policyDict2 } ] ) { 'Status' : 'Banned', 'Reason' : 'blah 2' } >>> pdp._combineSingePolicyResults( [ { 'Status' : 'Active', 'Reason' : 'blah', 'Policy' : policyDict }, { 'Status' : 'Active', 'Reason' : 'blah 2', 'Policy' : policyDict2 } ] ) { 'Status' : 'Banned', 'Reason' : 'blah ### blah 2' } :Parameters: **singlePolicyRes** - `list( dict )` list with every single policy result to be combined ( see _runPolicy for more details ) :return: S_OK( dict( Status, Reason ) | S_ERROR """ # Dictionary to be returned policyCombined = { 'Status' : 'Unknown', # default, it should be overridden by the policies, if they exist 'Reason' : '' } # If there are no policyResults, we return Unknown if not singlePolicyRes: policyCombined['Reason'] = 'No policy applies to %(element)s, %(name)s, %(elementType)s' % self.decisionParams self.log.warn(policyCombined['Reason']) return S_OK( policyCombined ) # We set the rssMachine on the current state ( ensures it is a valid one ) # FIXME: probably this check can be done at takeDecision machineStatus = self.rssMachine.setState( self.decisionParams['status'] ) if not machineStatus['OK']: return machineStatus # Discard all single policy results which belongs to policies that have set # the option `doNotCombine` in the CS policiesToCombine = self._findPoliciesToCombine( singlePolicyRes ) # Sort policy results using ther statuses by most restrictive ( lower level first ) self.rssMachine.orderPolicyResults( policiesToCombine ) # As they have been sorted by most restrictive status, the first one is going # to be our candidate new state. Let's ask the RSSMachine if it allows us to # make such transition. candidateState = policiesToCombine[ 0 ]['Status'] nextState = self.rssMachine.getNextState( candidateState ) if not nextState['OK']: return nextState nextState = nextState['Value'] # If the RssMachine does not accept the candidate, return forcing message if candidateState != nextState: policyCombined['Status'] = nextState policyCombined['Reason'] = 'RssMachine forced status %s to %s' % ( candidateState, nextState ) return S_OK( policyCombined ) # If the RssMachine accepts the candidate, just concatenate the reasons for policyRes in policiesToCombine: if policyRes['Status'] == nextState: policyCombined['Reason'] += '%s ###' % policyRes['Reason'] policyCombined['Status'] = nextState return S_OK( policyCombined ) def _findPoliciesToCombine( self, singlePolicyRes ): """ method that iterates over the single policy results and checks the CS configuration of the policies looking for the option 'doNotCombine'. If it is present, that single policy result is discarded. :Parameters: **singlePolicyRes** - `list( dict )` list with every single policy result to be combined ( see _runPolicy for more details ) :return: `list( dict )` """ # Get policies configuration from the CS. We want to exclude the policies that # have set the option `doNotCombine` from this process. policiesConfiguration = RssConfiguration.getPolicies() if not policiesConfiguration['OK']: return policiesConfiguration policiesConfiguration = policiesConfiguration['Value'] # Function that let's us know if we should combine the result of a single policy # or not. def combinePolicy( policyResult ): # Extract policy name from the dictionary returned by PolicyCaller policyName = policyResult['Policy']['name'] try: # If doNotCombineResult is defined, the policy is not taken into account # to create the combined result. However, the single policy result remains _ = policiesConfiguration[ policyName ]['doNotCombineResult'] return False except KeyError: return True # Make a list of policies of which we want to merge their results return [ policyResult for policyResult in singlePolicyRes if combinePolicy( policyResult ) ]
class PDP: """ The PDP (Policy Decision Point) module is used to: 1. Decides which policies have to be applied. 2. Invokes an evaluation of the policies, and returns the result (to a PEP) """ def __init__( self, clients ): ''' Constructor. Defines members that will be used later on. ''' self.pCaller = PolicyCaller( clients = clients ) self.iGetter = InfoGetter() self.decissionParams = {} self.rssMachine = RSSMachine( 'Unknown' ) def setup( self, decissionParams = None ): standardParamsDict = { 'element' : None, 'name' : None, 'elementType' : None, 'statusType' : None, 'status' : None, 'reason' : None, 'tokenOwner' : None, # Last parameter allows policies to be deactivated 'active' : 'Active' } if decissionParams is not None: standardParamsDict.update( decissionParams ) self.decissionParams = standardParamsDict ################################################################################ def takeDecision( self ):#, policyIn = None, argsIn = None, knownInfo = None ): """ PDP MAIN FUNCTION decides policies that have to be applied, based on __granularity, __name, __status, __formerStatus __reason If more than one policy is evaluated, results are combined. Logic for combination: a conservative approach is followed (i.e. if a site should be banned for at least one policy, that's what is returned) returns: { 'PolicyType': a policyType (in a string), 'Action': True|False, 'Status': 'Active'|'Probing'|'Banned', 'Reason': a reason #'EndDate: datetime.datetime (in a string)} """ policiesThatApply = self.iGetter.getPoliciesThatApply( self.decissionParams ) if not policiesThatApply[ 'OK' ]: return policiesThatApply policiesThatApply = policiesThatApply[ 'Value' ] singlePolicyResults = self._runPolicies( policiesThatApply ) if not singlePolicyResults[ 'OK' ]: return singlePolicyResults singlePolicyResults = singlePolicyResults[ 'Value' ] policyCombinedResults = self._combineSinglePolicyResults( singlePolicyResults ) if not policyCombinedResults[ 'OK' ]: return policyCombinedResults policyCombinedResults = policyCombinedResults[ 'Value' ] #FIXME: should also pass the result of the combination to the InfoGetter ? policyActionsThatApply = self.iGetter.getPolicyActionsThatApply( self.decissionParams ) if not policyActionsThatApply[ 'OK' ]: return policyActionsThatApply policyActionsThatApply = policyActionsThatApply[ 'Value' ] policyCombinedResults[ 'PolicyAction' ] = policyActionsThatApply return S_OK( { 'singlePolicyResults' : singlePolicyResults, 'policyCombinedResult' : policyCombinedResults, 'decissionParams' : self.decissionParams } ) ################################################################################ def _runPolicies( self, policies, decissionParams = None ): if decissionParams is None: decissionParams = self.decissionParams validStatus = RssConfiguration.getValidStatus() if not validStatus[ 'OK' ]: return validStatus validStatus = validStatus[ 'Value' ] policyInvocationResults = [] for policyDict in policies: policyInvocationResult = self.pCaller.policyInvocation( decissionParams, policyDict ) if not policyInvocationResult[ 'OK' ]: # We should never enter this line ! Just in case there are policies # missconfigured ! _msg = 'runPolicies no OK: %s' % policyInvocationResult gLogger.error( _msg ) return S_ERROR( _msg ) policyInvocationResult = policyInvocationResult[ 'Value' ] if not 'Status' in policyInvocationResult: _msg = 'runPolicies (no Status): %s' % policyInvocationResult gLogger.error( _msg ) return S_ERROR( _msg ) if not policyInvocationResult[ 'Status' ] in validStatus: _msg = 'runPolicies ( not valid status ) %s' % policyInvocationResult[ 'Status' ] gLogger.error( _msg ) return S_ERROR( _msg ) if not 'Reason' in policyInvocationResult: _msg = 'runPolicies (no Reason): %s' % policyInvocationResult gLogger.error( _msg ) return S_ERROR( _msg ) policyInvocationResults.append( policyInvocationResult ) return S_OK( policyInvocationResults ) ################################################################################ def _combineSinglePolicyResults( self, singlePolicyRes ): ''' singlePolicyRes = [ { 'State' : X, 'Reason' : Y, ... }, ... ] If there are no policyResults, returns Unknown as there are no policies to apply. Order elements in list by state, being the lowest the most restrictive one in the hierarchy. ''' # Dictionary to be returned policyCombined = { 'Status' : None, 'Reason' : '' } # If there are no policyResults, we return Unknown if not singlePolicyRes: _msgTuple = ( self.decissionParams[ 'element' ], self.decissionParams[ 'name' ], self.decissionParams[ 'elementType' ] ) policyCombined[ 'Status' ] = 'Unknown' policyCombined[ 'Reason' ] = 'No policy applies to %s, %s, %s' % _msgTuple return S_OK( policyCombined ) # We set the rssMachine on the current state machineStatus = self.rssMachine.setState( self.decissionParams[ 'status' ] ) if not machineStatus[ 'OK' ]: return machineStatus # Order statuses by most restrictive ( lower level first ) self.rssMachine.orderPolicyResults( singlePolicyRes ) #policyResults = self.rssMachine.orderPolicyResults( singlePolicyRes ) # Get according to the RssMachine the next state, given a candidate candidateState = singlePolicyRes[ 0 ][ 'Status' ] nextState = self.rssMachine.getNextState( candidateState ) if not nextState[ 'OK' ]: return nextState nextState = nextState[ 'Value' ] # If the RssMachine does not accept the candidate, return forcing message if candidateState != nextState: policyCombined[ 'Status' ] = nextState policyCombined[ 'Reason' ] = 'RssMachine forced status %s to %s' % ( candidateState, nextState ) return S_OK( policyCombined ) # If the RssMachine accepts the candidate, just concatenate the reasons for policyRes in singlePolicyRes: if policyRes[ 'Status' ] == nextState: policyCombined[ 'Reason' ] += '%s ###' % policyRes[ 'Reason' ] policyCombined[ 'Status' ] = nextState return S_OK( policyCombined ) ################################################################################ # def __useOldPolicyRes( self, name, policyName ): # ''' # Use the RSS Service to get an old policy result. # If such result is older than 2 hours, it returns {'Status':'Unknown'} # ''' # res = self.clients[ 'ResourceManagementClient' ].getPolicyResult( name = name, policyName = policyName ) # # if not res[ 'OK' ]: # return { 'Status' : 'Unknown' } # # res = res[ 'Value' ] # # if res == []: # return { 'Status' : 'Unknown' } # # res = res[ 0 ] # # oldStatus = res[ 5 ] # oldReason = res[ 6 ] # lastCheckTime = res[ 8 ] # # if ( lastCheckTime + datetime.timedelta(hours = 2) ) < datetime.datetime.utcnow(): # return { 'Status' : 'Unknown' } # # result = {} # # result[ 'Status' ] = oldStatus # result[ 'Reason' ] = oldReason # result[ 'OLD' ] = True # result[ 'PolicyName' ] = policyName # # return result ################################################################################ #EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF