Integrate Azure Functions deployment with In-Vitro#569
Integrate Azure Functions deployment with In-Vitro#569cavinkavi wants to merge 1 commit intovhive-serverless:mainfrom
Conversation
|
Looks like there is an error in the checks.
You can find more details about these requirements in our contributing guide: |
f717c64 to
bae6305
Compare
|
Hi @leokondrashov, I've completed my initial review of this PR. @cavinkavi has made the corresponding changes, and the current version looks good to me. Do you have any additional feedback or concerns? |
leokondrashov
left a comment
There was a problem hiding this comment.
First, there are no tests for the infrastructure, please add. Better even add them into CI tests.
Second, does Azure support Go deployments? We don't use Python that much and I'm not sure that our main Python implementation is well-behaved. It would be preferable to use Go instead.
| # Simulate the busySpin function | ||
| def busy_spin(duration_ms: int) -> None: | ||
| end_time = perf_counter() + duration_ms / 1000 # Convert ms to seconds | ||
| while perf_counter() < end_time: | ||
| continue | ||
|
|
||
| # Convert TraceFunctionExecution | ||
| def trace_function_execution(start: float, time_left_milliseconds: int) -> str: | ||
| time_consumed_milliseconds = int((time.time() - start) * 1000) | ||
| if time_consumed_milliseconds < time_left_milliseconds: | ||
| time_left_milliseconds -= time_consumed_milliseconds | ||
| if time_left_milliseconds > 0: | ||
| busy_spin(time_left_milliseconds) | ||
|
|
||
| return f"OK - {hostname}" |
There was a problem hiding this comment.
Please reuse this parts from existing Python implementation (server/trace-func-py/trace_func.py). Don't reimplement because this creates duplicates in the code and incorrect performance comparisons.
|
No need to throw out the Python deployment, though. It could be an option to deploy one of them during runtime. But the default should be Go to align with other deployments. |
leokondrashov
left a comment
There was a problem hiding this comment.
Also, please change the message of your commit to something meaningful. Addressing my comments doesn't make any sense when you are looking at the history of commits.
| } | ||
|
|
||
| // Helper function to copy files | ||
| func copyFile(src, dst string) error { |
There was a problem hiding this comment.
Put this function in utils
There was a problem hiding this comment.
Here you have the end-to-end test. Please add a test that would check the potential zip file for being operational. This wouldn't require the Azure credentials, so we can run it without any concerns about spending. This test would show when we have problems with our file copies in the archive.
Please also add them to the CI, so we can see the results right in the PRs.
At the same time, we can have an end-to-end test by just running the loader.go with the correct config file and checking for the output. There is no reason to add the script that duplicates the experiment pipeline. Better to repurpose it to test specific parts of the pipeline (deploying/cleaning functions, checking for the zip health).
There was a problem hiding this comment.
In that case, would it be better if I remove the azure_functions_test.go file and create an End-to-End test under workflows, which will run the go command and check the output as you have mentioned?
There was a problem hiding this comment.
Yes, similar to existing e2e tests. But still, add the module tests that I mentioned. For them, you would need the *_test.go files. And don't forget to add them into CI.
9fc7f13 to
528ebf3
Compare
leokondrashov
left a comment
There was a problem hiding this comment.
And still, the commit name doesn't make sense. In this PR, you introduce the Azure Function deployment. Please put this as the first line of the commit message (and the only, I don't see the reason to put any other comments there apart from sign-offs, feel free to disagree).
There was a problem hiding this comment.
Please add separate CI for that
.github/workflows/e2e_azure.yaml
Outdated
| - name: Install Golang | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: 1.21 |
There was a problem hiding this comment.
Look at other CI workflows and use same version (AWS e2e is outdated as well but we need to rework whole that part)
.github/workflows/e2e_azure.yaml
Outdated
| - name: Run Zip File Health Test | ||
| run: go test -v ./pkg/driver/deployment/azure_functions_test.go -run TestZipHealth | ||
|
|
||
| - name: Run Cleanup Test | ||
| run: go test -v ./pkg/driver/deployment/azure_functions_test.go -run TestCleanup |
There was a problem hiding this comment.
This should be separate CI, not combined with e2e.
There was a problem hiding this comment.
Please also add the unit test for deployment logic. I would want to see an easy troubleshooting route for failures of e2e test. Unit tests should cover most of the steps separately so we would see which ones fail and work on that instead of going into logs of e2e.
| def extract_execute_function(src_path): | ||
| # Extract the execute_function logic from trace_func.py. | ||
| with open(src_path, "r") as f: | ||
| content = f.read() | ||
|
|
||
| # Use regex to extract the execute_function definition and body | ||
| match = re.search(r"def execute_function\(.*?\):.*?(?=def |\Z)", content, re.DOTALL) | ||
| if not match: | ||
| raise ValueError("execute_function() not found in trace_func.py") | ||
|
|
||
| return match.group(0) | ||
|
|
||
| def inject_function(func_code, workload_path): |
There was a problem hiding this comment.
I don't know how this slipped from the previous review.
Please don't look inside the file and parse it to extract the implementation during deployment. What I proposed is to pull this function into a separate file and reuse it from both places. This way, during deployment, you only need to copy the file and import. I don't trust parsing the files, regexp are not as robust as you would think.
docs/loader.md
Outdated
| 2. Log in as a user (Note: This will open a browser window to select Azure account): | ||
| ```bash | ||
| az login | ||
| ``` |
There was a problem hiding this comment.
This is not always possible. There are no browser available on cloudlab nodes. Can you provide pure CLI method of logging in?
6de44b0 to
1d2e3b9
Compare
There was a problem hiding this comment.
It is exactly the same as in the server/trace-func-py. Please leave only one of them.
And add the other one in .gitignore file so we won't see it added in git status after we do deployment. Also add the note in the main py file for Azure deployment near the execute_function call about the fact that you need to copy it for local development.
docs/loader.md
Outdated
| --- | ||
| Notes: | ||
|
|
||
| - Service Principal must be created before running experiment, as some environments do not have browsers (e.g. CloudLab). Perform these steps in an environment that allows launching of browser and use the generated credentials. |
There was a problem hiding this comment.
| - Service Principal must be created before running experiment, as some environments do not have browsers (e.g. CloudLab). Perform these steps in an environment that allows launching of browser and use the generated credentials. | |
| - Service Principal must be created before running the experiment, as some environments do not GUI (e.g., CloudLab nodes). You can perform these steps in an environment that allows the launching of the browser and use the generated credentials. |
Overall, can this be done via Azure web console? This is much simpler than doing it via CLI, since this part you don't need to do in experiment environment.
There was a problem hiding this comment.
Instead of whole this file, you can add the directory into modules to test in unit-tests.yaml. Either way, you run all of the tests that you wrote in azure_functions_test.go but you specify all of them one-by-one, which is prone to errors (add test, forgot to test or the opposite, it fails because you removed one).
| func mockExecCommand(output string, err error) deployment.CommandRunner { | ||
| return func(name string, arg ...string) *exec.Cmd { | ||
| cmd := exec.Command("echo", output) // Simulate success | ||
| if err != nil { | ||
| return exec.Command("false") // Simulate failure | ||
| } | ||
| return cmd | ||
| } | ||
| } |
There was a problem hiding this comment.
What does this thing do? I only see it used once with err=nil. It overloads the code with unnecessary flexibility.
| assert.Contains(t, logs, "Deployed all 2 functions successfully") | ||
| } | ||
|
|
||
| // Tests CleanUpDeploymentFiles function correctly removes the specified temporary files and directories. |
There was a problem hiding this comment.
This test tests just two rm commands. It seems excessive for me. When I was talking about cleanup I meant the cleaning up the deployment in Azure.
| } | ||
|
|
||
| // Tests DeployFunction function by simulating deployment of zip file to Azure. | ||
| func TestDeployFunction(t *testing.T) { |
There was a problem hiding this comment.
This test just deploys the functions without cleaning them. So, my guess is that it fails on the second time onwards because functions already exist. Please test cleaning them up in the same test, so we can see whether it works. Also, be careful with the collision of tests. I.e., you shouldn't mistakenly remove the e2e functions that happen to run at the same time.
|
|
||
| func ZipFunctionAppFiles() error { | ||
| // Use bash to zip the contents of azure_functions_for_zip/ along with host.json and requirements.txt | ||
| cmd := exec.Command("bash", "-c", "cd azure_functions_for_zip && zip -r ../azurefunctions.zip . && cd .. && zip -j azurefunctions.zip azurefunctions_setup/host.json azurefunctions_setup/requirements.txt") |
There was a problem hiding this comment.
Please add all of the temporary directories and files into .gitignore
|
|
||
| // Deploy the zip file to Azure Function App using CLI | ||
| cmd := runner("az", "functionapp", "deployment", "source", "config-zip", | ||
| "--name", config.AzureConfig.FunctionAppName, |
There was a problem hiding this comment.
There is an issue with deploying all functions into the same application. In Azure, the scaling unit is an application, so when you invoke one of the functions, it will instantiate all of the functions in the app. So, if you call other functions in the application, it would be a warm start instead of a cold start. We should model independent scaling for all functions, so they should be in different applications.
|
|
||
| /* Functions for deploying zipped functions */ | ||
|
|
||
| func DeployFunction(config *Config, function []*common.Function, runner CommandRunner) error { |
There was a problem hiding this comment.
BTW, this name is confusing, it deploys all the functions, not just one
b8a34f2 to
398ec6e
Compare
Signed-off-by: Kavithran <104263022+cavinkavi@users.noreply.github.com>
Summary
A small summary of the requirements (in one/two sentences).
To integrate Azure Functions deployment with In-Vitro. This feature enables deployment of serverless functions on the Azure Functions platform.
Implementation Notes ⚒️
This PR adds a workflow to deploy multiple functions to Azure Functions via ZIP deployment.
Documentation on the steps to deploy the functions to Azure are included in
loader.md.azurefunctions_setup: this folder contains the relevant config and workload files to be used for deployment.azure_functions.go: initializes Azure resources required for deployment to Azure Functions, such as Resource Group, Storage Account, and Function App. In order to deploy multiple functions via ZIP, each function must have its own specific folder containing required dependencies, before zipping and deploying all the function folders as a single package. After successful deployment, local temporary folders holding the functions and the zip package are cleaned up.(Note: All cleanup, including local temporary folders, will be done after entire experiment is completed.)
External Dependencies 🍀
Breaking API Changes⚠️
Simply specify none (N/A) if not applicable.