1# -*- coding: utf-8 -*- # 2# Copyright 2018 Google LLC. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Wraps a Cloud Run Condition messages, making fields easier to access.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import unicode_literals 20 21import collections 22 23 24_SEVERITY_ERROR = 'Error' 25_SEVERITY_WARNING = 'Warning' 26 27 28def GetNonTerminalMessages(conditions, ignore_retry=False): 29 """Get messages for non-terminal subconditions. 30 31 Only show a message for some non-terminal subconditions: 32 - if severity == warning 33 - if message is provided 34 Non-terminal subconditions that aren't warnings are effectively neutral, 35 so messages for these aren't included unless provided. 36 37 Args: 38 conditions: Conditions 39 ignore_retry: bool, if True, ignores the "Retry" condition 40 41 Returns: 42 list(str) messages of non-terminal subconditions 43 """ 44 messages = [] 45 for c in conditions.NonTerminalSubconditions(): 46 if ignore_retry and c == 'Retry': 47 continue 48 if conditions[c]['severity'] == _SEVERITY_WARNING: 49 messages.append('{}: {}'.format( 50 c, conditions[c]['message'] or 'Unknown Warning.')) 51 elif conditions[c]['message']: 52 messages.append('{}: {}'.format(c, conditions[c]['message'])) 53 return messages 54 55 56class Conditions(collections.Mapping): 57 """Represents the status Conditions of a resource in a dict-like way. 58 59 Resource means a Cloud Run resource, e.g: Configuration. 60 61 The conditions of a resource describe error, warning, and completion states of 62 the last set of operations on the resource. True is success, False is failure, 63 and "Unknown" is an operation in progress. 64 65 The special "ready condition" describes the overall success state of the 66 (last operation on) the resource. 67 68 Other conditions may be "terminal", in which case they are required to be True 69 for overall success of the operation, and being False indicates failure. 70 71 If a condition has a severity of "info" or "warning" in the API, it's not 72 terminal. 73 74 More info: https://github.com/knative/serving/blob/master/docs/spec/errors.md 75 76 Note, status field of conditions is converted to boolean type. 77 """ 78 79 def __init__( 80 self, conditions, ready_condition=None, 81 observed_generation=None, generation=None): 82 """Constructor. 83 84 Args: 85 conditions: A list of objects of condition_class. 86 ready_condition: str, The one condition type that indicates it is ready. 87 observed_generation: The observedGeneration field of the status object 88 generation: The generation of the object. Incremented every time a user 89 changes the object directly. 90 """ 91 self._conditions = {} 92 for cond in conditions: 93 status = None # Unset or Unknown 94 if cond.status.lower() == 'true': 95 status = True 96 elif cond.status.lower() == 'false': 97 status = False 98 self._conditions[cond.type] = { 99 'severity': cond.severity, 100 'reason': cond.reason, 101 'message': cond.message, 102 'lastTransitionTime': cond.lastTransitionTime, 103 'status': status 104 } 105 self._ready_condition = ready_condition 106 self._fresh = (observed_generation is None or 107 (observed_generation == generation)) 108 109 def __getitem__(self, key): 110 """Implements evaluation of `self[key]`.""" 111 return self._conditions[key] 112 113 def __contains__(self, item): 114 """Implements evaluation of `item in self`.""" 115 return any(cond_type == item for cond_type in self._conditions) 116 117 def __len__(self): 118 """Implements evaluation of `len(self)`.""" 119 return len(self._conditions) 120 121 def __iter__(self): 122 """Returns a generator yielding the condition types.""" 123 for cond_type in self._conditions: 124 yield cond_type 125 126 def TerminalSubconditions(self): 127 """Yields keys of the conditions which if all True, Ready should be true.""" 128 for k in self: 129 if (k != self._ready_condition and 130 (not self[k]['severity'] or self[k]['severity'] == _SEVERITY_ERROR)): 131 yield k 132 133 def NonTerminalSubconditions(self): 134 """Yields keys of the conditions which do not directly affect Ready.""" 135 for k in self: 136 if (k != self._ready_condition and self[k]['severity'] and 137 self[k]['severity'] != _SEVERITY_ERROR): 138 yield k 139 140 def TerminalCondition(self): 141 return self._ready_condition 142 143 def DescriptiveMessage(self): 144 """Descriptive message about what's happened to the last user operation.""" 145 if (self._ready_condition and 146 self._ready_condition in self and 147 self[self._ready_condition]['message']): 148 return self[self._ready_condition]['message'] 149 return None 150 151 def IsTerminal(self): 152 """True if the resource has finished the last operation, for good or ill. 153 154 conditions are considered terminal if and only if the ready condition is 155 either true or false. 156 157 Returns: 158 A bool representing if terminal. 159 """ 160 if not self._ready_condition: 161 raise NotImplementedError() 162 if not self._fresh: 163 return False 164 if self._ready_condition not in self._conditions: 165 return False 166 return self._conditions[self._ready_condition]['status'] is not None 167 168 def IsReady(self): 169 """Return True if the resource has succeeded its current operation.""" 170 if not self.IsTerminal(): 171 return False 172 return self._conditions[self._ready_condition]['status'] 173 174 def IsFailed(self): 175 """"Return True if the resource has failed its current operation.""" 176 return self.IsTerminal() and not self.IsReady() 177 178 def IsFresh(self): 179 return self._fresh 180