Skip to content

Commit

Permalink
fix(Accelerate): Added Locking to Layer Building (aws#358)
Browse files Browse the repository at this point in the history
* Added Locking to Layer Building

* Fixed LockChain Exception

* Added Comment
  • Loading branch information
CoshUS committed Jul 8, 2021
1 parent fd8c322 commit cfa5bbb
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 36 deletions.
2 changes: 1 addition & 1 deletion samcli/lib/providers/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Function(NamedTuple):
# to get credentials to run the container with. This gives a much higher fidelity simulation of cloud Lambda.
rolearn: Optional[str]
# List of Layers
layers: List
layers: List["LayerVersion"]
# Event
events: Optional[List]
# Metadata
Expand Down
4 changes: 4 additions & 0 deletions samcli/lib/sync/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ def stack_resource_names(self) -> List[str]:
return self._stack_resource_names


class MissingLockException(Exception):
"""Exception for not having an associated lock to be used."""


class UriNotFoundException(Exception):
"""Exception used for not having a URI field that the resource requires"""

Expand Down
6 changes: 3 additions & 3 deletions samcli/lib/sync/flows/function_sync_flow.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Base SyncFlow for Lambda Function"""
import logging
from typing import Any, Dict, List, Optional, TYPE_CHECKING, cast
from typing import Any, Dict, List, TYPE_CHECKING, cast

from boto3.session import Session

Expand All @@ -21,7 +21,7 @@
class FunctionSyncFlow(SyncFlow):
_function_identifier: str
_function_provider: SamFunctionProvider
_function: Optional[Function]
_function: Function
_lambda_client: Any
_lambda_waiter: Any
_lambda_waiter_config: Dict[str, Any]
Expand Down Expand Up @@ -57,7 +57,7 @@ def __init__(
)
self._function_identifier = function_identifier
self._function_provider = self._build_context.function_provider
self._function = self._function_provider.functions.get(self._function_identifier)
self._function = cast(Function, self._function_provider.functions.get(self._function_identifier))
self._lambda_client = None
self._lambda_waiter = None
self._lambda_waiter_config = {"Delay": 1, "MaxAttempts": 60}
Expand Down
29 changes: 15 additions & 14 deletions samcli/lib/sync/flows/layer_sync_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,20 @@ def set_up(self) -> None:

def gather_resources(self) -> None:
"""Build layer and ZIP it into a temp file in self._zip_file"""
builder = ApplicationBuilder(
self._build_context.collect_build_resources(self._layer_identifier),
self._build_context.build_dir,
self._build_context.base_dir,
self._build_context.cache_dir,
cached=False,
is_building_specific_resource=True,
manifest_path_override=self._build_context.manifest_path_override,
container_manager=self._build_context.container_manager,
mode=self._build_context.mode,
)
LOG.debug("%sBuilding Layer", self.log_prefix)
self._artifact_folder = builder.build().get(self._layer_identifier)
with self._get_lock_chain():
builder = ApplicationBuilder(
self._build_context.collect_build_resources(self._layer_identifier),
self._build_context.build_dir,
self._build_context.base_dir,
self._build_context.cache_dir,
cached=False,
is_building_specific_resource=True,
manifest_path_override=self._build_context.manifest_path_override,
container_manager=self._build_context.container_manager,
mode=self._build_context.mode,
)
LOG.debug("%sBuilding Layer", self.log_prefix)
self._artifact_folder = builder.build().get(self._layer_identifier)

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

def _get_resource_api_calls(self) -> List[ResourceAPICall]:
return []
return [ResourceAPICall(self._layer_identifier, ["Build"])]

def _get_latest_layer_version(self):
"""Fetches all layer versions from remote and returns the latest one"""
Expand Down
32 changes: 18 additions & 14 deletions samcli/lib/sync/flows/zip_function_sync_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,20 @@ def set_up(self) -> None:

def gather_resources(self) -> None:
"""Build function and ZIP it into a temp file in self._zip_file"""
builder = ApplicationBuilder(
self._build_context.collect_build_resources(self._function_identifier),
self._build_context.build_dir,
self._build_context.base_dir,
self._build_context.cache_dir,
cached=False,
is_building_specific_resource=True,
manifest_path_override=self._build_context.manifest_path_override,
container_manager=self._build_context.container_manager,
mode=self._build_context.mode,
)
LOG.debug("%sBuilding Function", self.log_prefix)
self._artifact_folder = builder.build().get(self._function_identifier)
with self._get_lock_chain():
builder = ApplicationBuilder(
self._build_context.collect_build_resources(self._function_identifier),
self._build_context.build_dir,
self._build_context.base_dir,
self._build_context.cache_dir,
cached=False,
is_building_specific_resource=True,
manifest_path_override=self._build_context.manifest_path_override,
container_manager=self._build_context.container_manager,
mode=self._build_context.mode,
)
LOG.debug("%sBuilding Function", self.log_prefix)
self._artifact_folder = builder.build().get(self._function_identifier)

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

def _get_resource_api_calls(self) -> List[ResourceAPICall]:
return []
resource_calls = list()
for layer in self._function.layers:
resource_calls.append(ResourceAPICall(layer.full_path, ["Build"]))
return resource_calls
6 changes: 3 additions & 3 deletions samcli/lib/sync/sync_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from samcli.lib.providers.provider import ResourceIdentifier, Stack
from samcli.lib.utils.lock_distributor import LockDistributor, LockChain
from samcli.lib.sync.exceptions import MissingPhysicalResourceError
from samcli.lib.sync.exceptions import MissingLockException, MissingPhysicalResourceError

if TYPE_CHECKING:
from samcli.commands.deploy.deploy_context import DeployContext
Expand Down Expand Up @@ -179,7 +179,7 @@ def _get_lock_key(logical_id: str, api_call: str) -> str:
"""
return logical_id + "_" + api_call

def _get_lock_chain(self) -> Optional[LockChain]:
def _get_lock_chain(self) -> LockChain:
"""Return a LockChain object for all the locks
Returns
Expand All @@ -189,7 +189,7 @@ def _get_lock_chain(self) -> Optional[LockChain]:
"""
if self._locks:
return LockChain(self._locks)
return None
raise MissingLockException("Missing Locks for LockChain")

def _get_resource(self, resource_identifier: str) -> Optional[Dict[str, Any]]:
"""Get a resource dict with resource identifier
Expand Down
14 changes: 13 additions & 1 deletion tests/unit/lib/sync/flows/test_layer_sync_flow.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import base64
import hashlib
from unittest import TestCase
from unittest.mock import Mock, patch, call, ANY, mock_open, PropertyMock
from unittest.mock import MagicMock, Mock, patch, call, ANY, mock_open, PropertyMock

from parameterized import parameterized

Expand Down Expand Up @@ -86,6 +86,8 @@ def test_setup_gather_resources(
given_file_checksum = Mock()
patched_file_checksum.return_value = given_file_checksum

self.layer_sync_flow._get_lock_chain = MagicMock()

self.layer_sync_flow.gather_resources()

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

self.layer_sync_flow._get_lock_chain.assert_called_once()
self.layer_sync_flow._get_lock_chain.return_value.__enter__.assert_called_once()
self.layer_sync_flow._get_lock_chain.return_value.__exit__.assert_called_once()

def test_compare_remote(self):
given_lambda_client = Mock()
self.layer_sync_flow._lambda_client = given_lambda_client
Expand Down Expand Up @@ -325,6 +331,12 @@ def test_get_latest_layer_version_error(self):
with self.assertRaises(NoLayerVersionsFoundError):
self.layer_sync_flow._get_latest_layer_version()

@patch("samcli.lib.sync.flows.layer_sync_flow.ResourceAPICall")
def test_get_resource_api_calls(self, resource_api_call_mock):
result = self.layer_sync_flow._get_resource_api_calls()
self.assertEqual(len(result), 1)
resource_api_call_mock.assert_called_once_with(self.layer_identifier, ["Build"])


class TestFunctionLayerReferenceSync(TestCase):
def setUp(self):
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/lib/sync/flows/test_zip_function_sync_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def test_gather_resources(
file_checksum_mock.return_value = "sha256_value"
sync_flow = self.create_function_sync_flow()

sync_flow._get_lock_chain = MagicMock()

sync_flow.set_up()
sync_flow.gather_resources()

Expand All @@ -54,6 +56,9 @@ def test_gather_resources(
make_zip_mock.assert_called_once_with("temp_folder" + os.sep + "data-uuid_value", "ArtifactFolder1")
file_checksum_mock.assert_called_once_with("zip_file", sha256_mock.return_value)
self.assertEqual("sha256_value", sync_flow._local_sha)
sync_flow._get_lock_chain.assert_called_once()
sync_flow._get_lock_chain.return_value.__enter__.assert_called_once()
sync_flow._get_lock_chain.return_value.__exit__.assert_called_once()

@patch("samcli.lib.sync.flows.zip_function_sync_flow.base64.b64decode")
@patch("samcli.lib.sync.sync_flow.Session")
Expand Down Expand Up @@ -146,3 +151,26 @@ def test_sync_s3(self, session_mock, getsize_mock, uploader_mock, exists_mock, r
FunctionName="PhysicalFunction1", S3Bucket="bucket_name", S3Key="bucket/key"
)
remove_mock.assert_called_once_with("zip_file")

@patch("samcli.lib.sync.flows.zip_function_sync_flow.ResourceAPICall")
def test_get_resource_api_calls(self, resource_api_call_mock):
build_context = MagicMock()
layer1 = MagicMock()
layer2 = MagicMock()
layer1.full_path = "Layer1"
layer2.full_path = "Layer2"
function_mock = MagicMock()
function_mock.layers = [layer1, layer2]
build_context.function_provider.functions.get.return_value = function_mock
sync_flow = ZipFunctionSyncFlow(
"Function1",
build_context=build_context,
deploy_context=MagicMock(),
physical_id_mapping={},
stacks=[MagicMock()],
)

result = sync_flow._get_resource_api_calls()
self.assertEqual(len(result), 2)
resource_api_call_mock.assert_any_call("Layer1", ["Build"])
resource_api_call_mock.assert_any_call("Layer2", ["Build"])

0 comments on commit cfa5bbb

Please sign in to comment.