Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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""" 

17OperationsService 

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

19 

20""" 

21 

22import logging 

23 

24import grpc 

25 

26from google.protobuf.empty_pb2 import Empty 

27 

28from buildgrid._exceptions import InvalidArgumentError 

29from buildgrid._protos.google.longrunning import operations_pb2_grpc, operations_pb2 

30from buildgrid.server._authentication import AuthContext, authorize 

31 

32 

33class OperationsService(operations_pb2_grpc.OperationsServicer): 

34 

35 def __init__(self, server): 

36 self.__logger = logging.getLogger(__name__) 

37 

38 self._instances = {} 

39 

40 operations_pb2_grpc.add_OperationsServicer_to_server(self, server) 

41 

42 # --- Public API --- 

43 

44 def add_instance(self, instance_name, instance): 

45 """Registers a new servicer instance. 

46 

47 Args: 

48 instance_name (str): The new instance's name. 

49 instance (OperationsInstance): The new instance itself. 

50 """ 

51 self._instances[instance_name] = instance 

52 

53 # --- Public API: Servicer --- 

54 

55 @authorize(AuthContext) 

56 def GetOperation(self, request, context): 

57 self.__logger.debug(f"GetOperation request from [{context.peer()}]") 

58 

59 try: 

60 name = request.name 

61 

62 instance_name = self._parse_instance_name(name) 

63 instance = self._get_instance(instance_name) 

64 

65 operation_name = self._parse_operation_name(name) 

66 operation = instance.get_operation(operation_name) 

67 op = operations_pb2.Operation() 

68 op.CopyFrom(operation) 

69 op.name = name 

70 return op 

71 

72 except InvalidArgumentError as e: 

73 self.__logger.error(e) 

74 context.set_details(str(e)) 

75 context.set_code(grpc.StatusCode.INVALID_ARGUMENT) 

76 

77 except Exception as e: 

78 self.__logger.exception( 

79 f"Unexpected error in GetOperation; request=[{request}]" 

80 ) 

81 context.set_code(grpc.StatusCode.INTERNAL) 

82 

83 return operations_pb2.Operation() 

84 

85 @authorize(AuthContext) 

86 def ListOperations(self, request, context): 

87 self.__logger.debug(f"ListOperations request from [{context.peer()}]") 

88 

89 try: 

90 # The request name should be the collection name 

91 # In our case, this is just the instance_name 

92 instance_name = request.name 

93 instance = self._get_instance(instance_name) 

94 

95 result = instance.list_operations(request.filter, 

96 request.page_size, 

97 request.page_token) 

98 

99 for operation in result.operations: 

100 operation.name = f"{instance_name}/{operation.name}" 

101 

102 return result 

103 

104 except InvalidArgumentError as e: 

105 # This is a client error. Don't log at error on the server side 

106 self.__logger.debug(e) 

107 context.set_details(str(e)) 

108 context.set_code(grpc.StatusCode.INVALID_ARGUMENT) 

109 

110 except Exception as e: 

111 self.__logger.exception( 

112 f"Unexpected error in ListOperations; request=[{request}]" 

113 ) 

114 context.set_code(grpc.StatusCode.INTERNAL) 

115 

116 return operations_pb2.ListOperationsResponse() 

117 

118 @authorize(AuthContext) 

119 def DeleteOperation(self, request, context): 

120 self.__logger.debug(f"DeleteOperation request from [{context.peer()}]") 

121 

122 try: 

123 name = request.name 

124 

125 instance_name = self._parse_instance_name(name) 

126 instance = self._get_instance(instance_name) 

127 

128 operation_name = self._parse_operation_name(name) 

129 instance.delete_operation(operation_name) 

130 

131 except InvalidArgumentError as e: 

132 self.__logger.error(e) 

133 context.set_details(str(e)) 

134 context.set_code(grpc.StatusCode.INVALID_ARGUMENT) 

135 

136 except Exception as e: 

137 self.__logger.exception( 

138 f"Unexpected error in DeleteOperation; request=[{request}]" 

139 ) 

140 context.set_code(grpc.StatusCode.INTERNAL) 

141 

142 return Empty() 

143 

144 @authorize(AuthContext) 

145 def CancelOperation(self, request, context): 

146 self.__logger.debug(f"CancelOperation request from [{context.peer()}]") 

147 

148 try: 

149 name = request.name 

150 

151 instance_name = self._parse_instance_name(name) 

152 instance = self._get_instance(instance_name) 

153 

154 operation_name = self._parse_operation_name(name) 

155 instance.cancel_operation(operation_name) 

156 

157 except InvalidArgumentError as e: 

158 self.__logger.error(e) 

159 context.set_details(str(e)) 

160 context.set_code(grpc.StatusCode.INVALID_ARGUMENT) 

161 

162 except Exception as e: 

163 self.__logger.exception( 

164 f"Unexpected error in CancelOperation; request=[{request}]" 

165 ) 

166 context.set_code(grpc.StatusCode.INTERNAL) 

167 

168 return Empty() 

169 

170 # --- Private API --- 

171 

172 def _parse_instance_name(self, name): 

173 """ If the instance name is not blank, 'name' will have the form 

174 {instance_name}/{operation_uuid}. Otherwise, it will just be 

175 {operation_uuid} """ 

176 names = name.split('/') 

177 return '/'.join(names[:-1]) if len(names) > 1 else '' 

178 

179 def _parse_operation_name(self, name): 

180 names = name.split('/') 

181 return names[-1] if len(names) > 1 else name 

182 

183 def _get_instance(self, name): 

184 try: 

185 return self._instances[name] 

186 

187 except KeyError: 

188 raise InvalidArgumentError(f"Instance doesn't exist on server: [{name}]")