Skip to content

Commit

Permalink
Merge branch 'main' into features/vGsteiger/merge_functionality_retri…
Browse files Browse the repository at this point in the history
…eval
  • Loading branch information
vGsteiger authored Jan 9, 2024
2 parents d000901 + c6f345a commit 8c91fa5
Show file tree
Hide file tree
Showing 22 changed files with 187 additions and 88 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
uses: ./.github/actions/poetry
- name: Run tests
run: |
poetry run pytest --cache-clear --cov=multi_x_serverless multi_x_serverless/ > pytest-coverage.txt
poetry run pytest --cov-reset --cache-clear > pytest-coverage.txt
- name: Comment coverage
uses: coroo/[email protected]

Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ We are working with poetry, so you need to install it first.
pip install poetry
```

Alternatively if this doesn't work you will need to install poetry on Linux with:

```bash
apt install python3-poetry
```

Or you need to reinstall poetry:

```bash
curl -sSL https://install.python-poetry.org | python3 -
poetry self update
```

Then, install the dependencies:

```bash
Expand Down
14 changes: 12 additions & 2 deletions benchmarks/image_processing/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@
name="GetInput",
entry_point=True,
regions_and_providers={
"only_regions": [["aws", "us-east-1"], ["aws", "us-east-2"], ["aws", "us-west-1"], ["aws", "us-west-2"]],
"forbidden_regions": None,
"only_regions": [
{
"provider": "aws",
"region": "us-east-1",
}
],
"forbidden_regions": [
{
"provider": "aws",
"region": "us-east-2",
}
],
"providers": [
{
"name": "aws",
Expand Down
14 changes: 12 additions & 2 deletions benchmarks/regression_tuning/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,18 @@
name="GetInput",
entry_point=True,
regions_and_providers={
"only_regions": [["aws", "us-east-1"], ["aws", "us-east-2"], ["aws", "us-west-1"], ["aws", "us-west-2"]],
"forbidden_regions": None,
"only_regions": [
{
"provider": "aws",
"region": "us-east-1",
}
],
"forbidden_regions": [
{
"provider": "aws",
"region": "us-east-2",
}
],
"providers": [
{
"name": "aws",
Expand Down
14 changes: 12 additions & 2 deletions benchmarks/text_2_speech_censoring/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,18 @@
name="GetInput",
entry_point=True,
regions_and_providers={
"only_regions": [["aws", "us-east-1"], ["aws", "us-east-2"], ["aws", "us-west-1"], ["aws", "us-west-2"]],
"forbidden_regions": None,
"only_regions": [
{
"provider": "aws",
"region": "us-east-1",
}
],
"forbidden_regions": [
{
"provider": "aws",
"region": "us-east-2",
}
],
"providers": [
{
"name": "aws",
Expand Down
9 changes: 7 additions & 2 deletions docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,13 @@ workflow = MultiXServerlessWorkflow("workflow_name")
name="First-Function",
entry_point=True,
regions_and_providers={
"only_regions": [["aws", "us-east-1"]],
"forbidden_regions": [["aws", "us-east-2"]],
"only_regions": [
{
"provider": "aws",
"region": "us-east-1",
}
],
"forbidden_regions": None,
"providers": [
{
"name": "aws",
Expand Down
11 changes: 8 additions & 3 deletions multi_x_serverless/deployment/client/cli/config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,20 @@ def validate_config(cls: Any, values: Any) -> Any: # pylint: disable=no-self-ar
return values


class ProviderRegion(BaseModel):
provider: str = Field(..., title="The name of the provider")
region: str = Field(..., title="The name of the region")


class RegionAndProviders(BaseModel):
only_regions: Optional[List[List[str]]] = Field(None, title="List of regions to deploy to")
forbidden_regions: Optional[List[List[str]]] = Field(None, title="List of regions to not deploy to")
only_regions: Optional[List[ProviderRegion]] = Field(None, title="List of regions to deploy to")
forbidden_regions: Optional[List[ProviderRegion]] = Field(None, title="List of regions to not deploy to")
providers: List[Provider] = Field(..., title="List of possible providers with their configurations")


class ConfigSchema(BaseModel):
workflow_name: str = Field(..., title="The name of the workflow")
environment_variables: List[EnvironmentVariable] = Field(..., title="List of environment variables")
iam_policy_file: str = Field(..., title="The IAM policy file")
home_regions: List[List[str]] = Field(..., title="List of home regions")
home_regions: List[ProviderRegion] = Field(..., title="List of home regions")
regions_and_providers: RegionAndProviders = Field(..., title="List of regions and providers")
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ environment_variables:
- name: "ENV_VAR_1"
value: "value_1"
iam_policy_file: "iam_policy.json"
home_regions: [["aws", us-west-2"]] # Regions are defined as "provider:region" (e.g. aws:us-west-2)
home_regions:
- provider: "aws"
region: "us-east-1"
estimated_invocations_per_month: 1000000
constraints:
hard_resource_constraints: # None for none
Expand All @@ -25,8 +27,12 @@ constraints:
- runtime
- carbon
regions_and_providers: # Either the user specify only allowed regions (which will override everything else)
only_regions: null # Only check other conditions if this is None, else will restrict to ONLY those regions
forbidden_regions: null
only_regions:
- provider: "aws"
region: "us-east-1"
forbidden_regions:
- provider: "aws"
region: "us-east-2"
providers:
- name: "aws"
config:
Expand Down
14 changes: 12 additions & 2 deletions multi_x_serverless/deployment/client/cli/template/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@
name="First-Function",
entry_point=True,
regions_and_providers={
"only_regions": [["aws", "us-east-1"]],
"forbidden_regions": [["aws", "us-east-2"]],
"only_regions": [
{
"provider": "aws",
"region": "us-east-1",
}
],
"forbidden_regions": [
{
"provider": "aws",
"region": "us-east-2",
}
],
"providers": [
{
"name": "aws",
Expand Down
14 changes: 1 addition & 13 deletions multi_x_serverless/deployment/client/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,7 @@ def environment_variables(self) -> dict[str, Any]:
return env_variables

@property
def home_regions(self) -> list[tuple[str, str]]:
if "home_regions_tuple" in self.project_config:
return self.project_config["home_regions_tuple"]
home_regions: list[list[str]] = self._lookup("home_regions")
if home_regions is None:
return []
home_regions_tuple = [tuple(home_region[0:2]) for home_region in home_regions]
self.project_config["home_regions_tuple"] = home_regions_tuple
return home_regions_tuple # type: ignore
# somehow mypy thinks this is a list of tuples with one or more elements

@property
def home_regions_json(self) -> list[list[str]]:
def home_regions(self) -> list[dict[str, str]]:
return self._lookup("home_regions")

@property
Expand Down
4 changes: 2 additions & 2 deletions multi_x_serverless/deployment/client/deploy/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ def __init__(self, config: Config) -> None:

def execute(self, deployment_plan: DeploymentPlan) -> None:
for home_region, instructions in deployment_plan.instructions.items():
endpoint, region = home_region.split(":")
client = self._remote_client_factory.get_remote_client(endpoint, region)
provider, region = home_region.split(":")
client = self._remote_client_factory.get_remote_client(provider, region)
for instruction in instructions:
getattr(self, f"_do_{instruction.__class__.__name__.lower()}", self._default_handler)(
instruction,
Expand Down
32 changes: 16 additions & 16 deletions multi_x_serverless/deployment/client/deploy/models/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from multi_x_serverless.deployment.client.deploy.models.remote_state import RemoteState
from multi_x_serverless.deployment.client.deploy.models.resource import Resource
from multi_x_serverless.deployment.client.deploy.models.variable import Variable
from multi_x_serverless.deployment.client.enums import Endpoint
from multi_x_serverless.deployment.client.enums import Provider


class Function(Resource): # pylint: disable=too-many-instance-attributes
Expand All @@ -22,7 +22,7 @@ def __init__(
environment_variables: dict[str, str],
handler: str,
runtime: str,
home_regions: list[tuple[str, str]],
home_regions: list[dict[str, str]],
providers: list[dict],
) -> None:
super().__init__(name, "function")
Expand Down Expand Up @@ -50,12 +50,12 @@ def __repr__(self) -> str:
Providers: {self.providers}
"""

def initialise_remote_states(self, home_regions: list[tuple[str, str]]) -> None:
def initialise_remote_states(self, home_regions: list[dict[str, str]]) -> None:
for home_region in home_regions:
endpoint, region = home_region
if endpoint not in self._remote_states:
self._remote_states[endpoint] = {}
self._remote_states[endpoint][region] = RemoteState(endpoint=endpoint, region=region)
provider, region = home_region["provider"], home_region["region"]
if provider not in self._remote_states:
self._remote_states[provider] = {}
self._remote_states[provider][region] = RemoteState(provider=provider, region=region)

def dependencies(self) -> Sequence[Resource]:
resources: list[Resource] = [self.role, self.deployment_package]
Expand All @@ -64,21 +64,21 @@ def dependencies(self) -> Sequence[Resource]:
def get_deployment_instructions(self) -> dict[str, list[Instruction]]:
instructions: dict[str, list[Instruction]] = {}
for home_region in self.home_regions:
endpoint, region = home_region
if endpoint == Endpoint.AWS.value:
provider, region = home_region["provider"], home_region["region"]
if provider == Provider.AWS.value:
instruction = self.get_deployment_instructions_aws(region)
elif endpoint == Endpoint.GCP.value:
elif provider == Provider.GCP.value:
instruction = self.get_deployment_instructions_gcp(region)
else:
raise RuntimeError(f"Unknown endpoint {endpoint}")
instructions[f"{endpoint}:{region}"] = instruction
raise RuntimeError(f"Unknown provider {provider}")
instructions[f"{provider}:{region}"] = instruction
return instructions

def _get_memory_and_timeout(self) -> tuple[int, int]:
memory = 128
timeout = 3
for provider in self.providers:
if provider["name"] == Endpoint.AWS.value:
if provider["name"] == Provider.AWS.value:
if "memory" in provider:
memory = provider["memory"]
if "timeout" in provider:
Expand Down Expand Up @@ -117,7 +117,7 @@ def get_deployment_instructions_aws(self, region: str) -> list[Instruction]:
if policy is None:
raise RuntimeError(f"Lambda policy could not be read, check the path ({self.role.policy})")
policy = json.dumps(json.loads(policy))
if not self._remote_states[Endpoint.AWS.value][region].resource_exists(self.role):
if not self._remote_states[Provider.AWS.value][region].resource_exists(self.role):
instructions.extend(
[
APICall(
Expand Down Expand Up @@ -164,7 +164,7 @@ def get_deployment_instructions_aws(self, region: str) -> list[Instruction]:
with open(self.deployment_package.filename, "rb") as f:
zip_contents = f.read()
function_varname = f"{self.name}_lambda_arn_{region}"
if not self._remote_states[Endpoint.AWS.value][region].resource_exists(self):
if not self._remote_states[Provider.AWS.value][region].resource_exists(self):
instructions.extend(
[
APICall(
Expand Down Expand Up @@ -253,4 +253,4 @@ def get_sns_topic_instruction_for_region(self, region: str, output_var: str) ->
)

def get_deployment_instructions_gcp(self, region: str) -> list[Instruction]: # pylint: disable=unused-argument
return []
raise NotImplementedError()
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@


class RemoteState:
def __init__(self, endpoint: str, region: str) -> None:
self._client = RemoteClientFactory().get_remote_client(endpoint, region)
def __init__(self, provider: str, region: str) -> None:
self._client = RemoteClientFactory().get_remote_client(provider, region)

def resource_exists(self, resource: Resource) -> bool:
return self._client.resource_exists(resource)
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def get_description(self) -> WorkflowConfig:
raise RuntimeError("Error in workflow config creation, given config is None, this should not happen")
workflow_description = {
"instances": [function_instance.to_json() for function_instance in self._functions],
"start_hops": self._config.home_regions_json,
"start_hops": self._config.home_regions,
# TODO (#27): Implement and incorporate Free Tier considerations into data_sources
"estimated_invocations_per_month": self._config.estimated_invocations_per_month,
"constraints": self._config.constraints,
Expand Down
2 changes: 1 addition & 1 deletion multi_x_serverless/deployment/client/enums.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from enum import Enum


class Endpoint(Enum):
class Provider(Enum):
AWS = "aws"
GCP = "gcp"
18 changes: 9 additions & 9 deletions multi_x_serverless/deployment/client/factories/cli_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
create_default_deployer,
create_deletion_deployer,
)
from multi_x_serverless.deployment.client.enums import Endpoint
from multi_x_serverless.deployment.client.enums import Provider
from multi_x_serverless.deployment.client.multi_x_serverless_workflow import MultiXServerlessWorkflow


Expand Down Expand Up @@ -72,24 +72,24 @@ def __validate_only_regions_and_providers(self, project_config: dict) -> None:
if "providers" not in project_config["regions_and_providers"]:
raise RuntimeError("at least one provider must be defined in regions_and_providers")
if "only_regions" in project_config["regions_and_providers"]:
possible_endpoints = [endpoint.value for endpoint in Endpoint]
defined_endpoints = [
possible_providers = [provider.value for provider in Provider]
defined_providers = [
provider["name"]
for provider in project_config["regions_and_providers"]["providers"]
if provider["name"] in possible_endpoints
if provider["name"] in possible_providers
]
only_regions = project_config["regions_and_providers"]["only_regions"]
if not only_regions:
only_regions = []
if only_regions and not isinstance(only_regions, list):
raise RuntimeError("only_regions must be a list")
for region in only_regions:
if not isinstance(region, str):
for provider_region in only_regions:
if not isinstance(provider_region, dict):
raise RuntimeError("only_regions must be a list of strings")
provider = region[0]
if provider not in Endpoint.__members__:
provider = provider_region["provider"]
if provider not in Provider.__members__:
raise RuntimeError(f"Provider {provider} is not supported")
if provider not in defined_endpoints:
if provider not in defined_providers:
raise RuntimeError(f"Provider {provider} is not defined in providers")

def load_workflow_app(self) -> MultiXServerlessWorkflow:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from multi_x_serverless.deployment.client.enums import Endpoint
from multi_x_serverless.deployment.client.enums import Provider
from multi_x_serverless.deployment.client.remote_client.aws_remote_client import AWSRemoteClient
from multi_x_serverless.deployment.client.remote_client.remote_client import RemoteClient


class RemoteClientFactory:
def get_remote_client(self, endpoint: str, region: str) -> RemoteClient:
endpoint_enum = Endpoint(endpoint)
if endpoint_enum == Endpoint.AWS:
def get_remote_client(self, provider: str, region: str) -> RemoteClient:
provider_enum = Provider(provider)
if provider_enum == Provider.AWS:
return AWSRemoteClient(region)
if endpoint_enum == Endpoint.GCP:
if provider_enum == Provider.GCP:
raise NotImplementedError()
raise RuntimeError(f"Unknown endpoint {endpoint}")
raise RuntimeError(f"Unknown provider {provider}")
Loading

0 comments on commit 8c91fa5

Please sign in to comment.