Skip to content

Commit 38fd3cb

Browse files
nmadanNamrata Madanahsan-z-khan
authored andcommitted
feature: update lambda code on pipeline create/update/upsert for Lamb… (aws#3052)
* feature: update lambda code on pipeline create/update/upsert for LambdaStep * remove debugging statement * fix pylint Unnecessary "else" after "return" Co-authored-by: Namrata Madan <[email protected]> Co-authored-by: Ahsan Khan <[email protected]>
1 parent 4f58f92 commit 38fd3cb

File tree

4 files changed

+110
-50
lines changed

4 files changed

+110
-50
lines changed

src/sagemaker/lambda_helper.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,18 @@ def update(self):
161161
error = e.response["Error"]
162162
raise ValueError(error)
163163

164+
def upsert(self):
165+
"""Method to create a lambda function or update it if it already exists
166+
167+
Returns: boto3 response from Lambda's methods.
168+
"""
169+
try:
170+
return self.create()
171+
except ValueError as error:
172+
if "ResourceConflictException" in str(error):
173+
return self.update()
174+
raise
175+
164176
def invoke(self):
165177
"""Method to invoke a lambda function.
166178

src/sagemaker/workflow/lambda_step.py

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -149,28 +149,12 @@ def to_request(self) -> RequestType:
149149
return request_dict
150150

151151
def _get_function_arn(self):
152-
"""Returns the lamba function arn
152+
"""Returns the lambda function arn
153153
154154
Method creates a lambda function and returns it's arn.
155155
If the lambda is already present, it will build it's arn and return that.
156156
"""
157-
region = self.lambda_func.session.boto_region_name
158-
if region.lower() == "cn-north-1" or region.lower() == "cn-northwest-1":
159-
partition = "aws-cn"
160-
else:
161-
partition = "aws"
162-
163157
if self.lambda_func.function_arn is None:
164-
account_id = self.lambda_func.session.account_id()
165-
try:
166-
response = self.lambda_func.create()
167-
return response["FunctionArn"]
168-
except ValueError as error:
169-
if "ResourceConflictException" not in str(error):
170-
raise
171-
return (
172-
f"arn:{partition}:lambda:{region}:{account_id}:"
173-
f"function:{self.lambda_func.function_name}"
174-
)
175-
else:
176-
return self.lambda_func.function_arn
158+
response = self.lambda_func.upsert()
159+
return response["FunctionArn"]
160+
return self.lambda_func.function_arn

tests/unit/sagemaker/workflow/test_lambda_step.py

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -201,36 +201,40 @@ def test_lambda_step_no_inputs_outputs(sagemaker_session):
201201

202202

203203
def test_lambda_step_with_function_arn(sagemaker_session):
204+
lambda_func = MagicMock(
205+
function_arn="arn:aws:lambda:us-west-2:123456789012:function:sagemaker_test_lambda",
206+
session=sagemaker_session,
207+
)
204208
lambda_step = LambdaStep(
205209
name="MyLambdaStep",
206210
depends_on=["TestStep"],
207-
lambda_func=Lambda(
208-
function_arn="arn:aws:lambda:us-west-2:123456789012:function:sagemaker_test_lambda",
209-
session=sagemaker_session,
210-
),
211+
lambda_func=lambda_func,
211212
inputs={},
212213
outputs=[],
213214
)
214-
lambda_step._get_function_arn()
215-
sagemaker_session.account_id.assert_not_called()
215+
function_arn = lambda_step._get_function_arn()
216+
assert function_arn == "arn:aws:lambda:us-west-2:123456789012:function:sagemaker_test_lambda"
217+
lambda_func.upsert.assert_not_called()
216218

217219

218220
def test_lambda_step_without_function_arn(sagemaker_session):
221+
lambda_func = MagicMock(
222+
function_arn=None,
223+
function_name="name",
224+
execution_role_arn="arn:aws:lambda:us-west-2:123456789012:execution_role",
225+
zipped_code_dir="",
226+
handler="",
227+
session=sagemaker_session,
228+
)
219229
lambda_step = LambdaStep(
220230
name="MyLambdaStep",
221231
depends_on=["TestStep"],
222-
lambda_func=Lambda(
223-
function_name="name",
224-
execution_role_arn="arn:aws:lambda:us-west-2:123456789012:execution_role",
225-
zipped_code_dir="",
226-
handler="",
227-
session=sagemaker_session,
228-
),
232+
lambda_func=lambda_func,
229233
inputs={},
230234
outputs=[],
231235
)
232236
lambda_step._get_function_arn()
233-
sagemaker_session.account_id.assert_called_once()
237+
lambda_func.upsert.assert_called_once()
234238

235239

236240
def test_lambda_step_without_function_arn_and_with_error(sagemaker_session_cn):
@@ -242,26 +246,14 @@ def test_lambda_step_without_function_arn_and_with_error(sagemaker_session_cn):
242246
handler="",
243247
session=sagemaker_session_cn,
244248
)
245-
# The raised ValueError contains ResourceConflictException
246-
lambda_func.create.side_effect = ValueError("ResourceConflictException")
247-
lambda_step1 = LambdaStep(
248-
name="MyLambdaStep1",
249-
depends_on=["TestStep"],
250-
lambda_func=lambda_func,
251-
inputs={},
252-
outputs=[],
253-
)
254-
function_arn = lambda_step1._get_function_arn()
255-
assert function_arn == "arn:aws-cn:lambda:cn-north-1:234567890123:function:name"
256249

257-
# The raised ValueError does not contain ResourceConflictException
258-
lambda_func.create.side_effect = ValueError()
259-
lambda_step2 = LambdaStep(
260-
name="MyLambdaStep2",
250+
lambda_func.upsert.side_effect = ValueError()
251+
lambda_step = LambdaStep(
252+
name="MyLambdaStep",
261253
depends_on=["TestStep"],
262254
lambda_func=lambda_func,
263255
inputs={},
264256
outputs=[],
265257
)
266258
with pytest.raises(ValueError):
267-
lambda_step2._get_function_arn()
259+
lambda_step._get_function_arn()

tests/unit/test_lambda_helper.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,78 @@ def test_update_lambda_client_error(sagemaker_session):
294294
assert "Cannot update code" in str(error)
295295

296296

297+
@patch("sagemaker.lambda_helper._zip_lambda_code", return_value=ZIPPED_CODE)
298+
def test_upsert_lambda_happycase1(sagemaker_session):
299+
lambda_obj = lambda_helper.Lambda(
300+
function_name=FUNCTION_NAME,
301+
execution_role_arn=EXECUTION_ROLE,
302+
script=SCRIPT,
303+
handler=HANDLER,
304+
session=sagemaker_session,
305+
)
306+
307+
code = {"ZipFile": ZIPPED_CODE}
308+
lambda_obj.upsert()
309+
310+
sagemaker_session.lambda_client.create_function.assert_called_with(
311+
FunctionName=FUNCTION_NAME,
312+
Runtime="python3.8",
313+
Handler=HANDLER,
314+
Role=EXECUTION_ROLE,
315+
Code=code,
316+
Timeout=120,
317+
MemorySize=128,
318+
)
319+
320+
321+
@patch("sagemaker.lambda_helper._zip_lambda_code", return_value=ZIPPED_CODE)
322+
def test_upsert_lambda_happycase2(sagemaker_session):
323+
lambda_obj = lambda_helper.Lambda(
324+
function_name=FUNCTION_NAME,
325+
execution_role_arn=EXECUTION_ROLE,
326+
script=SCRIPT,
327+
handler=HANDLER,
328+
session=sagemaker_session,
329+
)
330+
331+
sagemaker_session.lambda_client.create_function.side_effect = ClientError(
332+
{"Error": {"Code": "ResourceConflictException", "Message": "Lambda already exists"}},
333+
"CreateFunction",
334+
)
335+
336+
lambda_obj.upsert()
337+
338+
sagemaker_session.lambda_client.update_function_code.assert_called_once_with(
339+
FunctionName=FUNCTION_NAME, ZipFile=ZIPPED_CODE
340+
)
341+
342+
343+
@patch("sagemaker.lambda_helper._zip_lambda_code", return_value=ZIPPED_CODE)
344+
def test_upsert_lambda_client_error(sagemaker_session):
345+
lambda_obj = lambda_helper.Lambda(
346+
function_name=FUNCTION_NAME,
347+
execution_role_arn=EXECUTION_ROLE,
348+
script=SCRIPT,
349+
handler=HANDLER,
350+
session=sagemaker_session,
351+
)
352+
353+
sagemaker_session.lambda_client.create_function.side_effect = ClientError(
354+
{"Error": {"Code": "ResourceConflictException", "Message": "Lambda already exists"}},
355+
"CreateFunction",
356+
)
357+
358+
sagemaker_session.lambda_client.update_function_code.side_effect = ClientError(
359+
{"Error": {"Code": "ResourceConflictException", "Message": "Cannot update code"}},
360+
"UpdateFunctionCode",
361+
)
362+
363+
with pytest.raises(ValueError) as error:
364+
lambda_obj.upsert()
365+
366+
assert "Cannot update code" in str(error)
367+
368+
297369
def test_invoke_lambda_happycase(sagemaker_session):
298370
lambda_obj = lambda_helper.Lambda(function_arn=LAMBDA_ARN, session=sagemaker_session)
299371
lambda_obj.invoke()

0 commit comments

Comments
 (0)