Skip to content

Commit cfa5bbb

Browse files
authored
fix(Accelerate): Added Locking to Layer Building (aws#358)
* Added Locking to Layer Building * Fixed LockChain Exception * Added Comment
1 parent fd8c322 commit cfa5bbb

8 files changed

Lines changed: 85 additions & 36 deletions

File tree

samcli/lib/providers/provider.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class Function(NamedTuple):
5151
# to get credentials to run the container with. This gives a much higher fidelity simulation of cloud Lambda.
5252
rolearn: Optional[str]
5353
# List of Layers
54-
layers: List
54+
layers: List["LayerVersion"]
5555
# Event
5656
events: Optional[List]
5757
# Metadata

samcli/lib/sync/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ def stack_resource_names(self) -> List[str]:
8686
return self._stack_resource_names
8787

8888

89+
class MissingLockException(Exception):
90+
"""Exception for not having an associated lock to be used."""
91+
92+
8993
class UriNotFoundException(Exception):
9094
"""Exception used for not having a URI field that the resource requires"""
9195

samcli/lib/sync/flows/function_sync_flow.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Base SyncFlow for Lambda Function"""
22
import logging
3-
from typing import Any, Dict, List, Optional, TYPE_CHECKING, cast
3+
from typing import Any, Dict, List, TYPE_CHECKING, cast
44

55
from boto3.session import Session
66

@@ -21,7 +21,7 @@
2121
class FunctionSyncFlow(SyncFlow):
2222
_function_identifier: str
2323
_function_provider: SamFunctionProvider
24-
_function: Optional[Function]
24+
_function: Function
2525
_lambda_client: Any
2626
_lambda_waiter: Any
2727
_lambda_waiter_config: Dict[str, Any]
@@ -57,7 +57,7 @@ def __init__(
5757
)
5858
self._function_identifier = function_identifier
5959
self._function_provider = self._build_context.function_provider
60-
self._function = self._function_provider.functions.get(self._function_identifier)
60+
self._function = cast(Function, self._function_provider.functions.get(self._function_identifier))
6161
self._lambda_client = None
6262
self._lambda_waiter = None
6363
self._lambda_waiter_config = {"Delay": 1, "MaxAttempts": 60}

samcli/lib/sync/flows/layer_sync_flow.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,20 @@ def set_up(self) -> None:
8383

8484
def gather_resources(self) -> None:
8585
"""Build layer and ZIP it into a temp file in self._zip_file"""
86-
builder = ApplicationBuilder(
87-
self._build_context.collect_build_resources(self._layer_identifier),
88-
self._build_context.build_dir,
89-
self._build_context.base_dir,
90-
self._build_context.cache_dir,
91-
cached=False,
92-
is_building_specific_resource=True,
93-
manifest_path_override=self._build_context.manifest_path_override,
94-
container_manager=self._build_context.container_manager,
95-
mode=self._build_context.mode,
96-
)
97-
LOG.debug("%sBuilding Layer", self.log_prefix)
98-
self._artifact_folder = builder.build().get(self._layer_identifier)
86+
with self._get_lock_chain():
87+
builder = ApplicationBuilder(
88+
self._build_context.collect_build_resources(self._layer_identifier),
89+
self._build_context.build_dir,
90+
self._build_context.base_dir,
91+
self._build_context.cache_dir,
92+
cached=False,
93+
is_building_specific_resource=True,
94+
manifest_path_override=self._build_context.manifest_path_override,
95+
container_manager=self._build_context.container_manager,
96+
mode=self._build_context.mode,
97+
)
98+
LOG.debug("%sBuilding Layer", self.log_prefix)
99+
self._artifact_folder = builder.build().get(self._layer_identifier)
99100

100101
zip_file_path = os.path.join(tempfile.gettempdir(), f"data-{uuid.uuid4().hex}")
101102
self._zip_file = make_zip(zip_file_path, self._artifact_folder)
@@ -179,7 +180,7 @@ def gather_dependencies(self) -> List[SyncFlow]:
179180
return dependencies
180181

181182
def _get_resource_api_calls(self) -> List[ResourceAPICall]:
182-
return []
183+
return [ResourceAPICall(self._layer_identifier, ["Build"])]
183184

184185
def _get_latest_layer_version(self):
185186
"""Fetches all layer versions from remote and returns the latest one"""

samcli/lib/sync/flows/zip_function_sync_flow.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,20 @@ def set_up(self) -> None:
7171

7272
def gather_resources(self) -> None:
7373
"""Build function and ZIP it into a temp file in self._zip_file"""
74-
builder = ApplicationBuilder(
75-
self._build_context.collect_build_resources(self._function_identifier),
76-
self._build_context.build_dir,
77-
self._build_context.base_dir,
78-
self._build_context.cache_dir,
79-
cached=False,
80-
is_building_specific_resource=True,
81-
manifest_path_override=self._build_context.manifest_path_override,
82-
container_manager=self._build_context.container_manager,
83-
mode=self._build_context.mode,
84-
)
85-
LOG.debug("%sBuilding Function", self.log_prefix)
86-
self._artifact_folder = builder.build().get(self._function_identifier)
74+
with self._get_lock_chain():
75+
builder = ApplicationBuilder(
76+
self._build_context.collect_build_resources(self._function_identifier),
77+
self._build_context.build_dir,
78+
self._build_context.base_dir,
79+
self._build_context.cache_dir,
80+
cached=False,
81+
is_building_specific_resource=True,
82+
manifest_path_override=self._build_context.manifest_path_override,
83+
container_manager=self._build_context.container_manager,
84+
mode=self._build_context.mode,
85+
)
86+
LOG.debug("%sBuilding Function", self.log_prefix)
87+
self._artifact_folder = builder.build().get(self._function_identifier)
8788

8889
zip_file_path = os.path.join(tempfile.gettempdir(), "data-" + uuid.uuid4().hex)
8990
self._zip_file = make_zip(zip_file_path, self._artifact_folder)
@@ -134,4 +135,7 @@ def sync(self) -> None:
134135
os.remove(self._zip_file)
135136

136137
def _get_resource_api_calls(self) -> List[ResourceAPICall]:
137-
return []
138+
resource_calls = list()
139+
for layer in self._function.layers:
140+
resource_calls.append(ResourceAPICall(layer.full_path, ["Build"]))
141+
return resource_calls

samcli/lib/sync/sync_flow.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from samcli.lib.providers.provider import ResourceIdentifier, Stack
1212
from samcli.lib.utils.lock_distributor import LockDistributor, LockChain
13-
from samcli.lib.sync.exceptions import MissingPhysicalResourceError
13+
from samcli.lib.sync.exceptions import MissingLockException, MissingPhysicalResourceError
1414

1515
if TYPE_CHECKING:
1616
from samcli.commands.deploy.deploy_context import DeployContext
@@ -179,7 +179,7 @@ def _get_lock_key(logical_id: str, api_call: str) -> str:
179179
"""
180180
return logical_id + "_" + api_call
181181

182-
def _get_lock_chain(self) -> Optional[LockChain]:
182+
def _get_lock_chain(self) -> LockChain:
183183
"""Return a LockChain object for all the locks
184184
185185
Returns
@@ -189,7 +189,7 @@ def _get_lock_chain(self) -> Optional[LockChain]:
189189
"""
190190
if self._locks:
191191
return LockChain(self._locks)
192-
return None
192+
raise MissingLockException("Missing Locks for LockChain")
193193

194194
def _get_resource(self, resource_identifier: str) -> Optional[Dict[str, Any]]:
195195
"""Get a resource dict with resource identifier

tests/unit/lib/sync/flows/test_layer_sync_flow.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import base64
22
import hashlib
33
from unittest import TestCase
4-
from unittest.mock import Mock, patch, call, ANY, mock_open, PropertyMock
4+
from unittest.mock import MagicMock, Mock, patch, call, ANY, mock_open, PropertyMock
55

66
from parameterized import parameterized
77

@@ -86,6 +86,8 @@ def test_setup_gather_resources(
8686
given_file_checksum = Mock()
8787
patched_file_checksum.return_value = given_file_checksum
8888

89+
self.layer_sync_flow._get_lock_chain = MagicMock()
90+
8991
self.layer_sync_flow.gather_resources()
9092

9193
self.build_context_mock.collect_build_resources.assert_called_with(self.layer_identifier)
@@ -112,6 +114,10 @@ def test_setup_gather_resources(
112114
self.assertEqual(self.layer_sync_flow._zip_file, given_zip_location)
113115
self.assertEqual(self.layer_sync_flow._local_sha, given_file_checksum)
114116

117+
self.layer_sync_flow._get_lock_chain.assert_called_once()
118+
self.layer_sync_flow._get_lock_chain.return_value.__enter__.assert_called_once()
119+
self.layer_sync_flow._get_lock_chain.return_value.__exit__.assert_called_once()
120+
115121
def test_compare_remote(self):
116122
given_lambda_client = Mock()
117123
self.layer_sync_flow._lambda_client = given_lambda_client
@@ -325,6 +331,12 @@ def test_get_latest_layer_version_error(self):
325331
with self.assertRaises(NoLayerVersionsFoundError):
326332
self.layer_sync_flow._get_latest_layer_version()
327333

334+
@patch("samcli.lib.sync.flows.layer_sync_flow.ResourceAPICall")
335+
def test_get_resource_api_calls(self, resource_api_call_mock):
336+
result = self.layer_sync_flow._get_resource_api_calls()
337+
self.assertEqual(len(result), 1)
338+
resource_api_call_mock.assert_called_once_with(self.layer_identifier, ["Build"])
339+
328340

329341
class TestFunctionLayerReferenceSync(TestCase):
330342
def setUp(self):

tests/unit/lib/sync/flows/test_zip_function_sync_flow.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def test_gather_resources(
4646
file_checksum_mock.return_value = "sha256_value"
4747
sync_flow = self.create_function_sync_flow()
4848

49+
sync_flow._get_lock_chain = MagicMock()
50+
4951
sync_flow.set_up()
5052
sync_flow.gather_resources()
5153

@@ -54,6 +56,9 @@ def test_gather_resources(
5456
make_zip_mock.assert_called_once_with("temp_folder" + os.sep + "data-uuid_value", "ArtifactFolder1")
5557
file_checksum_mock.assert_called_once_with("zip_file", sha256_mock.return_value)
5658
self.assertEqual("sha256_value", sync_flow._local_sha)
59+
sync_flow._get_lock_chain.assert_called_once()
60+
sync_flow._get_lock_chain.return_value.__enter__.assert_called_once()
61+
sync_flow._get_lock_chain.return_value.__exit__.assert_called_once()
5762

5863
@patch("samcli.lib.sync.flows.zip_function_sync_flow.base64.b64decode")
5964
@patch("samcli.lib.sync.sync_flow.Session")
@@ -146,3 +151,26 @@ def test_sync_s3(self, session_mock, getsize_mock, uploader_mock, exists_mock, r
146151
FunctionName="PhysicalFunction1", S3Bucket="bucket_name", S3Key="bucket/key"
147152
)
148153
remove_mock.assert_called_once_with("zip_file")
154+
155+
@patch("samcli.lib.sync.flows.zip_function_sync_flow.ResourceAPICall")
156+
def test_get_resource_api_calls(self, resource_api_call_mock):
157+
build_context = MagicMock()
158+
layer1 = MagicMock()
159+
layer2 = MagicMock()
160+
layer1.full_path = "Layer1"
161+
layer2.full_path = "Layer2"
162+
function_mock = MagicMock()
163+
function_mock.layers = [layer1, layer2]
164+
build_context.function_provider.functions.get.return_value = function_mock
165+
sync_flow = ZipFunctionSyncFlow(
166+
"Function1",
167+
build_context=build_context,
168+
deploy_context=MagicMock(),
169+
physical_id_mapping={},
170+
stacks=[MagicMock()],
171+
)
172+
173+
result = sync_flow._get_resource_api_calls()
174+
self.assertEqual(len(result), 2)
175+
resource_api_call_mock.assert_any_call("Layer1", ["Build"])
176+
resource_api_call_mock.assert_any_call("Layer2", ["Build"])

0 commit comments

Comments
 (0)