Coverage for /builds/BuildGrid/buildgrid/buildgrid/_exceptions.py: 92.39%

92 statements  

« prev     ^ index     » next       coverage.py v6.4.1, created at 2022-06-22 21:04 +0000

1# Copyright (C) 2018 Bloomberg LP 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# <http://www.apache.org/licenses/LICENSE-2.0> 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15 

16""" 

17Exceptions 

18=========== 

19""" 

20 

21 

22from collections import namedtuple 

23from enum import Enum 

24from datetime import timedelta 

25 

26import grpc 

27from google.protobuf.duration_pb2 import Duration 

28 

29from buildgrid._protos.google.rpc import error_details_pb2, status_pb2 

30 

31 

32# grpc.Status is a metaclass class, so we derive 

33# a local _Status and associate the expected Attributes 

34# with it 

35class _Status( 

36 namedtuple('_Status', 

37 ('code', 'details', 'trailing_metadata')), 

38 grpc.Status): 

39 pass 

40 

41 

42class BgdError(Exception): 

43 """ 

44 Base BuildGrid Error class for internal exceptions. 

45 """ 

46 

47 def __init__(self, message, *, detail=None, domain=None, reason=None): 

48 super().__init__(message) 

49 

50 # Additional detail and extra information 

51 self.detail = detail 

52 

53 # Domand and reason 

54 self.domain = domain 

55 self.reason = reason 

56 

57 

58class ErrorDomain(Enum): 

59 SERVER = 1 

60 BOT = 2 

61 

62 

63class ServerError(BgdError): 

64 def __init__(self, message, detail=None, reason=None): 

65 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

66 

67 

68class BotError(BgdError): 

69 def __init__(self, message, detail=None, reason=None): 

70 super().__init__(message, detail=detail, domain=ErrorDomain.BOT, reason=reason) 

71 

72 

73class CancelledError(BgdError): 

74 """The job was cancelled and any callers should be notified""" 

75 

76 def __init__(self, message, detail=None, reason=None): 

77 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

78 self.last_response = None 

79 

80 

81class InvalidArgumentError(BgdError): 

82 """A bad argument was passed, such as a name which doesn't exist.""" 

83 

84 def __init__(self, message, detail=None, reason=None): 

85 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

86 

87 

88class NotFoundError(BgdError): 

89 """Requested resource not found.""" 

90 

91 def __init__(self, message, detail=None, reason=None): 

92 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

93 

94 

95class UpdateNotAllowedError(BgdError): 

96 """UpdateNotAllowedError error.""" 

97 

98 def __init__(self, message, detail=None, reason=None): 

99 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

100 

101 

102class OutOfRangeError(BgdError): 

103 """ByteStream service read data out of range.""" 

104 

105 def __init__(self, message, detail=None, reason=None): 

106 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

107 

108 

109class FailedPreconditionError(BgdError): 

110 """One or more errors occurred in setting up the action requested, such as 

111 a missing input or command or no worker being available. The client may be 

112 able to fix the errors and retry.""" 

113 

114 def __init__(self, message, detail=None, reason=None): 

115 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

116 

117 

118class PermissionDeniedError(BgdError): 

119 """The caller does not have permission to execute the specified operation.""" 

120 

121 def __init__(self, message, detail=None, reason=None): 

122 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

123 

124 

125class BotSessionError(BgdError): 

126 """Parent class of BotSession Exceptions""" 

127 

128 

129class BotSessionClosedError(BotSessionError): 

130 """The requested BotSession has been closed recently.""" 

131 

132 def __init__(self, message, detail=None, reason=None): 

133 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

134 

135 

136class UnknownBotSessionError(BotSessionError): 

137 """Buildgrid does not know the requested BotSession.""" 

138 

139 def __init__(self, message, detail=None, reason=None): 

140 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

141 

142 

143class BotSessionMismatchError(BotSessionError): 

144 """The BotSession details don't match those in BuildGrid's records.""" 

145 

146 def __init__(self, message, detail=None, reason=None): 

147 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

148 

149 

150class DuplicateBotSessionError(BotSessionError): 

151 """The bot with this ID already has a BotSession.""" 

152 

153 def __init__(self, message, detail=None, reason=None): 

154 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

155 

156 

157class DatabaseError(BgdError): 

158 """BuildGrid encountered a database error""" 

159 

160 def __init__(self, message, detail=None, reason=None): 

161 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

162 

163 

164class RetriableError(BgdError): 

165 """BuildGrid encountered a retriable error 

166 `retry_info` to instruct clients when to retry 

167 `error_status` a grpc.Status message suitable to call with context.abort_with_status() 

168 """ 

169 

170 def __init__(self, message, retry_period: timedelta, detail=None, reason=None): 

171 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

172 retry_delay = Duration() 

173 retry_delay.FromTimedelta(retry_period) 

174 retry_info = error_details_pb2.RetryInfo(retry_delay=retry_delay) 

175 

176 # We could get the integer value of the UNAVAILABLE 

177 # status code using grpc.StatusCode.UNAVAILABLE.value[0], 

178 # but the grpc-stubs library complains if we do that. So 

179 # instead we just hardcode the value, which is unlikely to 

180 # change as it's a standard code: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md 

181 status_proto = status_pb2.Status( 

182 code=14, 

183 message=message) 

184 error_detail = status_proto.details.add() 

185 error_detail.Pack(retry_info) 

186 

187 error_status = _Status( 

188 code=grpc.StatusCode.UNAVAILABLE, 

189 details=status_proto.message, 

190 trailing_metadata=(('grpc-status-details-bin', status_proto.SerializeToString()),) 

191 ) 

192 self.retry_info = retry_info 

193 self.error_status = error_status 

194 

195 

196class RetriableDatabaseError(RetriableError): 

197 """BuildGrid encountered a retriable database error""" 

198 

199 def __init__(self, message, retry_period: timedelta, detail=None, reason=None): 

200 super().__init__(message, retry_period, detail=detail, reason=reason) 

201 

202 

203class StreamAlreadyExistsError(BgdError): 

204 """BuildGrid tried to create a LogStream that already exists.""" 

205 

206 def __init__(self, message, detail=None, reason=None): 

207 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

208 

209 

210class StreamFinishedError(BgdError): 

211 """BuildGrid tried to append to a finished LogStream.""" 

212 

213 def __init__(self, message, detail=None, reason=None): 

214 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

215 

216 

217class StreamWritePendingError(BgdError): 

218 """BuildGrid tried to read a chunk of the stream that has not been commited yet.""" 

219 

220 def __init__(self, message, detail=None, reason=None): 

221 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

222 

223 

224class StorageFullError(BgdError): 

225 """BuildGrid's storage is full, cannot commit to it.""" 

226 

227 def __init__(self, message, detail=None, reason=None): 

228 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason) 

229 

230 

231class GrpcUninitializedError(BgdError): 

232 """BuildGrid tried to use a gRPC stub before gRPC was initialized.""" 

233 

234 def __init__(self, message, detail=None, reason=None): 

235 super().__init__(message, detail=detail, domain=ErrorDomain.SERVER, reason=reason)