Coverage for /builds/BuildGrid/buildgrid/buildgrid/server/actioncache/caches/with_cache.py: 78.72%

47 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-03-28 16:20 +0000

1# Copyright (C) 2020 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 

16import logging 

17from contextlib import ExitStack 

18from typing import Tuple 

19 

20from buildgrid._exceptions import NotFoundError 

21from buildgrid._protos.build.bazel.remote.execution.v2.remote_execution_pb2 import ActionResult, Digest 

22from buildgrid.server.actioncache.caches.action_cache_abc import ActionCacheABC 

23 

24LOGGER = logging.getLogger(__name__) 

25 

26 

27class WithCacheActionCache(ActionCacheABC): 

28 """ 

29 An ActionCache that first makes use of a storage and fallback cache 

30 

31 """ 

32 

33 def __init__( 

34 self, cache: ActionCacheABC, fallback: ActionCacheABC, allow_updates: bool, cache_failed_actions: bool 

35 ) -> None: 

36 """Initialise a new Action Cache with a fallback cache. 

37 

38 Args: 

39 cache (ActionCacheABC): Local cache backend instance to be used. 

40 fallback(ActionCacheABC): Fallback backend instance to be used 

41 allow_updates(bool): Allow updates pushed to the Action Cache. 

42 Defaults to ``True``. 

43 cache_failed_actions(bool): Whether to store failed (non-zero exit 

44 code) actions. Default to ``True``. 

45 """ 

46 super().__init__(allow_updates=allow_updates) 

47 self._stack = ExitStack() 

48 self._cache_failed_actions = cache_failed_actions 

49 self._cache = cache 

50 self._fallback = fallback 

51 

52 def start(self) -> None: 

53 self._stack.enter_context(self._cache) 

54 self._stack.enter_context(self._fallback) 

55 

56 def stop(self) -> None: 

57 self._stack.close() 

58 

59 def get_action_result(self, action_digest: Digest) -> ActionResult: 

60 """Retrieves the cached result for an Action. 

61 

62 Will first attempt to retrieve result from cache and then fallback. A 

63 NotFoundError is raised if both cache and fallback return None. 

64 

65 Args: 

66 action_digest (Digest): The digest of the Action to retrieve the 

67 cached result of. 

68 

69 """ 

70 cache_result = None 

71 fallback_result = None 

72 key = self._get_key(action_digest) 

73 try: 

74 cache_result = self._cache.get_action_result(action_digest) 

75 except NotFoundError: 

76 pass 

77 except Exception: 

78 LOGGER.exception(f"Unexpected error in cache get_action_result; action_digest=[{action_digest}]") 

79 

80 if cache_result is None: 

81 fallback_result = self._fallback.get_action_result(action_digest) 

82 else: 

83 return cache_result 

84 

85 if fallback_result is not None: 

86 self._cache.update_action_result(action_digest, fallback_result) 

87 return fallback_result 

88 

89 raise NotFoundError(f"Key not found: {key}") 

90 

91 def update_action_result(self, action_digest: Digest, action_result: ActionResult) -> None: 

92 """Stores a result for an Action in the cache. 

93 

94 Will attempt to store result in cache, and then in fallback cache. If 

95 the result has a non-zero exit code and `cache_failed_actions` is False 

96 for this cache, the result is not cached. 

97 

98 Args: 

99 action_digest (Digest): The digest of the Action whose result is 

100 being cached. 

101 action_result (ActionResult): The result to cache for the given 

102 Action digest. 

103 

104 """ 

105 if self._cache_failed_actions or action_result.exit_code == 0: 

106 if not self._allow_updates: 

107 raise NotImplementedError("Updating cache not allowed") 

108 

109 try: 

110 self._cache.update_action_result(action_digest, action_result) 

111 

112 except Exception: 

113 LOGGER.warning( 

114 f"Failed to cache action [{action_digest.hash}/{action_digest.size_bytes}]", exc_info=True 

115 ) 

116 

117 self._fallback.update_action_result(action_digest, action_result) 

118 

119 def _get_key(self, action_digest: Digest) -> Tuple[str, int]: 

120 """Get a hashable cache key from a given Action digest. 

121 

122 Args: 

123 action_digest (Digest): The digest to produce a cache key for. 

124 

125 """ 

126 return (action_digest.hash, action_digest.size_bytes)